Bladeren bron

Merge branch 'test' of http://34.206.75.59:10880/ASJ_ADS/ads_web into test

guojing_wu 1 jaar geleden
bovenliggende
commit
752a35b820
24 gewijzigde bestanden met toevoegingen van 2339 en 878 verwijderingen
  1. 55 30
      src/views/adManage/sp/ads/index.vue
  2. 58 0
      src/views/adManage/sp/advertisedProducts/api.ts
  3. 261 0
      src/views/adManage/sp/advertisedProducts/chartComponents/dataTendency.vue
  4. 174 0
      src/views/adManage/sp/advertisedProducts/crud.tsx
  5. 54 4
      src/views/adManage/sp/advertisedProducts/index.vue
  6. 79 67
      src/views/adManage/sp/campaigns/chartComponents/adStruct.vue
  7. 208 200
      src/views/adManage/sp/campaigns/chartComponents/dataTendency.vue
  8. 205 191
      src/views/adManage/sp/campaigns/crud.tsx
  9. 1 1
      src/views/adManage/sp/campaigns/index.vue
  10. 4 4
      src/views/adManage/sp/chartComponents/dataTendency.vue
  11. 61 63
      src/views/adManage/sp/keywords/chartComponents/adStruct.vue
  12. 19 11
      src/views/adManage/sp/keywords/crud.tsx
  13. 52 28
      src/views/adManage/sp/placement/api.ts
  14. 393 0
      src/views/adManage/sp/placement/chartComponents/adStruct.vue
  15. 261 0
      src/views/adManage/sp/placement/chartComponents/dataTendency.vue
  16. 155 86
      src/views/adManage/sp/placement/crud.tsx
  17. 78 27
      src/views/adManage/sp/placement/index.vue
  18. 65 72
      src/views/adManage/sp/targets/chartComponents/adStruct.vue
  19. 61 33
      src/views/adManage/sp/targets/chartComponents/dataTendency.vue
  20. 36 33
      src/views/adManage/sp/targets/crud.tsx
  21. 0 1
      src/views/adManage/sp/targets/index.vue
  22. 0 6
      src/views/adManage/sp/targets/utils/dropdowndisable.js
  23. 6 0
      src/views/adManage/utils/dropdowndisable.ts
  24. 53 21
      src/views/adManage/utils/enum.ts

+ 55 - 30
src/views/adManage/sp/ads/index.vue

@@ -1,44 +1,69 @@
 <template>
-	<fs-page class="fs-page-custom">
-    <fs-crud ref="crudRef" v-bind="crudBinding">
-			<template #header-middle>
-        <MetricsCards v-model="metrics" :metric-items="options" @change="changeMetric"></MetricsCards>
-				<div style="height: 500px; border: 1px solid red;">图形区域</div>
-      </template>
-		</fs-crud>
-	</fs-page>
+    <fs-page class="fs-page-custom">
+        <fs-crud ref="crudRef" v-bind="crudBinding">
+            <template #header-middle>
+                <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card" @tab-change="changeTab">
+                    <el-tab-pane label="数据趋势" name="dataTendency">
+                        <DataTendencyChart ref="dataTendencyRef"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="广告结构" name="adStruct" :lazy="true">
+                        <AdStructChart ref="adStructChartRef"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点视图" name="scatterView" :lazy="true"></el-tab-pane>
+                </el-tabs>
+            </template>
+        </fs-crud>
+    </fs-page>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, onBeforeUnmount, watch, nextTick, onActivated } from 'vue';
-import { useFs, FsPage } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { useRoute, useRouter } from 'vue-router'
-import MetricsCards from '/@/components/MetricsCards/index.vue'
+import {ref, onMounted, nextTick, inject} from 'vue'
+import {useFs, FsPage} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useRoute, useRouter} from 'vue-router'
+import DataTendencyChart from './chartComponents/dataTendency.vue'
+import {useShopInfo} from '/@/stores/shopInfo'
+import AdStructChart from './chartComponents/adStruct.vue'
+
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const dateRange = inject('dateRange')
+const start = dateRange.value[0]
+const end = dateRange.value[1]
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: {profileId: shopInfo.profile.profile_id, start: start, end: end}})
 
 const route = useRoute()
-const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {adGroupId: route.query.adGroupId} });
-const metrics = ref([{metric: 'ACOS', color: 'blue'}])
-const options = ref([
-  {label: 'ACOS', value: 'ACOS', metricVal: "18.00%", preVal: '20.15%', gapVal: '-2.00%', disabled:true},
-  {label: '点击量', value: 'clicks', metricVal: "19.00%", preVal: '20.15%', gapVal: '-1.00%', disabled:true},
-  {label: '曝光量', value: 'impression', metricVal: "20.00%", preVal: '15.00%', gapVal: '5.00%', disabled:true},
-  {label: '转化率1', value: 'rate1', metricVal: "1.00%", preVal: '15.00%', gapVal: '5.00%', disabled:true},
-  {label: '转化率2', value: 'rate2', metricVal: "2.00%", preVal: '15.00%', gapVal: '5.00%', disabled:true},
-  {label: '转化率3', value: 'rate3', metricVal: "3.00%", preVal: '15.00%', gapVal: '5.00%', disabled:true},
-  {label: '转化率4', value: 'rate4', metricVal: "4.00%", preVal: '15.00%', gapVal: '5.00%'},
-  {label: '转化率5', value: 'rate5', metricVal: "5.00%", preVal: '15.00%', gapVal: '5.00%'},
-  {label: '转化率6', value: 'rate6', metricVal: "6.00%", preVal: '15.00%', gapVal: '5.00%'},
-])
+const router = useRouter()
+const adStructChartRef = ref()
+const dataTendencyRef = ref()
+
 
 onMounted(() => {
-	crudExpose.doRefresh();
+    crudExpose.doRefresh()
 })
-const changeMetric = () => {
 
+const resizeTabChart = () => {
+    if (tabActiveName.value === 'dataTendency') {
+        dataTendencyRef.value.resizeChart()
+    } else if (tabActiveName.value === 'adStruct') {
+        adStructChartRef.value.resizeChart()
+    }
+}
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
 }
+defineExpose({resizeTabChart})
 </script>
 
-<style scoped>
+<style lang="scss">
+.chart-tabs {
+    margin: 5px 0;
+
+    .el-tabs__nav {
+        padding-left: 0 !important;
+    }
+}
 
-</style>
+</style>

+ 58 - 0
src/views/adManage/sp/advertisedProducts/api.ts

@@ -0,0 +1,58 @@
+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/spads/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        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 + "total/",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineData(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + "daily/",
+        method: 'GET',
+        params: query
+    })
+}

+ 261 - 0
src/views/adManage/sp/advertisedProducts/chartComponents/dataTendency.vue

@@ -0,0 +1,261 @@
+<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, onBeforeMount, watch, computed} from 'vue'
+import * as echarts from 'echarts'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {getLineData, getCardData} from '../api'
+import {spCampaignMetricsEnum} from '/@/views/adManage/utils/enum.js'
+import MetricsCards from '/@/components/MetricsCards/index.vue'
+import XEUtils from 'xe-utils'
+import {buildChartOpt} from '/@/views/adManage/utils/tools.js'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+
+defineOptions({
+    name: 'DataTendencyChart'
+})
+
+onMounted(() => {
+    getMetricsItems()
+    addResize()
+    // initLine()
+    setTimeout(() => {
+        initLine()
+    }, 0)
+})
+onBeforeUnmount(() => {
+    if (chartObj) {
+        chartObj.dispose()
+        chartObj = null
+    }
+    removeResize()
+})
+
+const publicData = usePublicData()
+const metrics = ref([
+    {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
+    {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
+    {metric: 'Spend', color: '#ff9500', 'label': '花费'},
+])
+const shopInfo = useShopInfo()
+const metricsItems: Ref<MetricData[]> = ref([])
+let chartObj: any
+const chartRef = ref()
+const option: any = {
+    dataset: {
+        source: []
+    },
+    tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+            label: {
+                backgroundColor: '#6a7985'
+            }
+        }
+    },
+    legend: {
+        selected: {},  // 控制显隐
+        show: false
+    },
+    grid: {
+        top: 50, right: 150, bottom: 30, left: 55,
+    },
+    xAxis: {
+        type: 'category'
+    },
+    yAxis: [
+        {
+            id: 0,
+            type: 'value',
+            name: '曝光量',
+            splitLine: {
+                show: true // 设置显示分割线
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#0085ff'
+                }
+            },
+            show: true
+        },
+        {
+            id: 1,
+            type: 'value',
+            name: '点击量',
+            position: 'right',
+            splitLine: {
+                show: false
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#3fd4cf'
+                }
+            },
+            show: true
+        },
+        {
+            id: 2,
+            type: 'value',
+            position: 'right',
+            offset: 90,
+            name: '花费',
+            splitLine: {
+                show: false
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#ff9500'
+                }
+            },
+            show: true
+        }
+    ],
+    series: [
+        {
+            id: 0,
+            name: '曝光量',
+            type: 'bar',
+            encode: {
+                x: 'date',
+                y: 'Impression'
+            },
+            barWidth: '20px',
+            yAxisIndex: 0,
+            itemStyle: {
+                color: '#0085ff',
+                borderRadius: [6, 6, 6, 6],
+            }
+        },
+        {
+            id: 1,
+            name: '点击量',
+            type: 'line',
+            encode: {
+                x: 'date',
+                y: 'Click'
+            },
+            symbolSize: 6,
+            symbol: 'circle',
+            smooth: true,
+            yAxisIndex: 1,
+            itemStyle: {color: '#3fd4cf', borderColor: '#3fd4cf'},
+            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: 'date',
+                y: 'Spend'
+            },
+            symbolSize: 6,
+            symbol: 'circle',
+            smooth: true,
+            yAxisIndex: 2,
+            itemStyle: {color: '#ff9500', borderColor: '#ff9500'},
+            areaStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                    {offset: 0, color: '#ff950053'},
+                    {offset: 1, color: '#ff950003'},
+                ]),
+            },
+            emphasis: {
+                focus: 'series'
+            }
+        }
+    ]
+}
+const {dateRange} = storeToRefs(publicData)
+const loading = ref(true)
+
+const getDataset = async () => {
+    const resp = await getLineData({profile: shopInfo.profile.profile_id, start: dateRange.value[0], end: dateRange.value[1]})
+    return resp.data
+}
+const initLine = async () => {
+    chartObj = echarts.init(chartRef.value)
+    const items = await getDataset()
+    option.dataset.source = items
+    XEUtils.arrayEach(metricsItems.value, info => {
+        option.legend.selected[info.label] = false
+    })
+    for (const info of metrics.value) {
+        option.legend.selected[info.label] = true
+    }
+    chartObj.setOption(option)
+    loading.value = false
+}
+
+const getMetricsItems = async () => {
+    const resp = await getCardData({start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id})
+    const data = resp.data
+    metricsItems.value.length = 0
+    XEUtils.arrayEach(spCampaignMetricsEnum, 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)
+}
+
+watch(
+        dateRange,
+        async () => {
+            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%;
+}
+</style>

+ 174 - 0
src/views/adManage/sp/advertisedProducts/crud.tsx

@@ -0,0 +1,174 @@
+import * as api from './api'
+import {dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {dynBidStrategyEnum} from '/@/views/adManage/utils/enum.js'
+
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+    const pageRequest = async (query: UserPageQuery) => {
+        query['profile'] = context['profileId']
+        query['start'] = context['start']
+        query['end'] = context['end']
+        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
+                    },
+                }
+            },
+            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: {
+                        iconRight: 'Delete',
+                        type: 'text',
+                        text: null
+                        // show: hasPermissions('dictionary:Delete'),
+                    },
+                },
+            },
+            columns: {
+                id: {
+                    title: 'ID',
+                    column: {
+                        show: false
+                    },
+                    form: {
+                        show: false
+                    }
+                },
+                asin: {title: '商品',
+                    column: {
+                        align: 'center',
+                        fixed: 'left'
+                    },},
+                state: {
+                    title: '状态',
+                    column: {
+                        width: '90px',
+                        align: 'center'
+                    },
+                    type: 'dict-select',
+                    search: {
+                        show: true
+                    },
+                    dict: dict({
+                        data: [
+                            {value: 'PAUSED', label: '已暂停', color: 'warning'},
+                            {value: 'ENABLED', label: '投放中', color: 'success'},
+                        ]
+                    })
+                },
+                campaignName: {
+                    title: '广告活动名称',
+                    column: {
+                        width: '200px',
+                        align: 'center',
+                    },
+                    search: {
+                        show: true,
+                        component: {
+                            props: {
+                                clearable: true
+                            }
+                        }
+                    },
+                    form: {
+                        rules: [{required: true, message: '必填项'}]
+                    }
+                },
+                adGroupName: {
+                    title: '广告组名称',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                bid: {
+                    title: '出价',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                '标签': {},
+                Impression: {
+                    title: '曝光量',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                Click: {
+                    title: '点击量',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                CTR: {title: '点击率'},
+                Spend: {title: '花费'},
+                CPC: {title: '点击成本'},
+                TotalPurchases: {title: '订单数'},
+                TotalSales: {title: '销售额'},
+                TotalUnitOrdered: {title: '销量'},
+                PurchasesRate: {title: '转化率'},
+                ACOS: {title: 'ACOS'},
+                ROAS: {title: 'ROAS'},
+                CPA: {title: '订单成本'},
+                ...BaseColumn
+            }
+        }
+    }
+}

+ 54 - 4
src/views/adManage/sp/advertisedProducts/index.vue

@@ -1,12 +1,62 @@
 <template>
-
+    <fs-page class="fs-page-custom">
+        <fs-crud ref="crudRef" v-bind="crudBinding">
+            <template #header-middle>
+                <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card" @tab-change="changeTab">
+                    <el-tab-pane label="数据趋势" name="dataTendency">
+                        <DataTendencyChart ref="dataTendencyRef"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点视图" name="scatterView" :lazy="true"></el-tab-pane>
+                </el-tabs>
+            </template>
+        </fs-crud>
+    </fs-page>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue'
+import {ref, onMounted, nextTick, inject} from 'vue'
+import {useFs, FsPage} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useRoute, useRouter} from 'vue-router'
+import DataTendencyChart from './chartComponents/dataTendency.vue'
+import {useShopInfo} from '/@/stores/shopInfo'
+
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const dateRange = inject('dateRange')
+const start = dateRange.value[0]
+const end = dateRange.value[1]
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: {profileId: shopInfo.profile.profile_id, start: start, end: end}})
+
+const route = useRoute()
+const router = useRouter()
+const dataTendencyRef = ref()
 
+
+onMounted(() => {
+    crudExpose.doRefresh()
+})
+
+const resizeTabChart = () => {
+    if (tabActiveName.value === 'dataTendency') {
+        dataTendencyRef.value.resizeChart()
+    }
+}
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
+}
+defineExpose({resizeTabChart})
 </script>
 
-<style scoped>
+<style lang="scss">
+.chart-tabs {
+    margin: 5px 0;
+
+    .el-tabs__nav {
+        padding-left: 0 !important;
+    }
+}
 
-</style>
+</style>

+ 79 - 67
src/views/adManage/sp/campaigns/chartComponents/adStruct.vue

@@ -3,16 +3,18 @@
         <el-row :gutter="5">
             <el-col :span="9">
                 <div>
-                    <TextSelector v-model="modelValue" :options="pieOptions" @change="changePie" style="margin-top: 5px"/>
+                    <TextSelector v-model="modelValue" :options="computedPieOptions" @change="changePie" style="margin-top: 5px"/>
                 </div>
                 <div ref="pie" style="height: 400px;"></div>
             </el-col>
             <el-col :span="15">
                 <div style="margin-left: 40%">
                     <span style="background: #3a83f7; width: 18px; height: 10px; margin-top: 8px; display: inline-block; border-radius: 3px;"></span>
-                    <TextSelector v-model="barModelValue1" :options="barOptions1" @change="changeBarOne" style="margin-top: 5px; margin-left: 8px;"/>
+                    <TextSelector v-model="barModelValue1" :options="computedBarOptions1" @change="changeBarOne"
+                                  style="margin-top: 5px; margin-left: 8px;"/>
                     <span style="background: #f19a37; width: 18px; height: 10px; margin-top: 8px; margin-left: 20px; display: inline-block; border-radius: 3px;"></span>
-                    <TextSelector v-model="barModelValue2" :options="barOptions2" @change="changeBarTwo" style="margin-top: 5px; margin-left: 8px;"/>
+                    <TextSelector v-model="barModelValue2" :options="computedBarOptions2" @change="changeBarTwo"
+                                  style="margin-top: 5px; margin-left: 8px;"/>
                 </div>
                 <div ref="bar" style="height: 400px;"></div>
             </el-col>
@@ -21,25 +23,23 @@
 </template>
 
 <script setup>
-import { onMounted, ref, inject, watch, nextTick } from "vue"
+import { computed, inject, onMounted, ref, watch } from "vue"
 import * as echarts from "echarts"
 import TextSelector from '/@/components/TextSelector/index.vue'
 import { getAdStructureData } from "/@/views/adManage/sp/campaigns/api"
+import { createDisabledOptions } from '../../../utils/dropdowndisable'
+import { barOptionsMap, metricMap } from '/@/views/adManage/utils/enum'
+import { useShopInfo } from '/@/stores/shopInfo'
 
+const shopInfo = useShopInfo()
 let pieChart = ref()
 let barChart = ref()
 const pie = ref()
 const bar = ref()
 const loading = ref(true)
 
-let dateRange = inject('dateRange')
-console.log('dateRange', dateRange.value)
-watch(
-    dateRange,
-    () => {
-        console.log('-------watch dateRange', dateRange.value)
-    }
-)
+const dateRange = inject('dateRange')
+
 // 下拉框相关
 const pieOptions = [
     {
@@ -67,14 +67,6 @@ const pieOptions = [
         label: '点击量',
     },
 ]
-const metricMap = {
-    'Spend': '花费',
-    'TotalSales': '销售额',
-    'TotalPurchases': '订单数',
-    'TotalUnitOrdered': '销量',
-    'Impression': '曝光量',
-    'Click': '点击量',
-}
 let modelValue = ref(pieOptions[0].value)
 
 const barOptions1 = [
@@ -256,7 +248,7 @@ onMounted(async () => {
 let allData = null
 
 async function setAdStructureData() {
-    allData = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: '3006125408623189' })
+    allData = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id })
     console.log('allData.data', allData.data)
     return allData.data
 }
@@ -268,8 +260,8 @@ let pieBarData = null
 // 柱状图初始数据
 let ACOSList
 let SpendList
-let ClassificationList
-let classificationMapList
+let xAxisList
+let xAxisMapList
 
 async function initPieBarData() {
     pieBarData = await setAdStructureData()
@@ -282,7 +274,7 @@ async function initPieBarData() {
     ACOSList = barData.map(item => item.ACOS)
     SpendList = barData.map(item => item.Spend)
     // 将x轴映射为中文
-    ClassificationList = barData.map(item => item.Classification)
+    xAxisList = barData.map(item => item.Classification)
     const classificationMap = {
         'BROAD': '关键词-广泛',
         'category': '品类',
@@ -294,17 +286,16 @@ async function initPieBarData() {
         'substitutes': '同类商品',
         'complements': '关联商品'
     }
-    classificationMapList = ClassificationList.map(item => classificationMap[item])
+    xAxisMapList = xAxisList.map(item => classificationMap[item])
     loading.value = false
 }
 
 // 重置图像
 let flag = ref()
-let barFlag = ref()
-let barFlag2 = ref()
 let option
 let option2
 
+// 点击下拉框后重新渲染饼图
 function changePie(newValue) {
     modelValue.value = newValue
     flag.value = modelValue.value
@@ -312,44 +303,63 @@ function changePie(newValue) {
         { value: pieBarData.pie_data[0][flag.value], name: '自动' },
         { value: pieBarData.pie_data[1][flag.value], name: '手动' },
     ]
-    // pieChart.clear() // 清除当前饼图的配置
-    // option2.series[0].data = newPieData // 确保更新的是数组中的正确位置
-    // pieChart.setOption(option2, true) // 使用 true 强制合并选项
     pieChart.setOption(option2)
 }
 
+// 点击下拉框后重新渲染柱状图
 function changeBarOne(newValue) {
     barModelValue1.value = newValue
-    barFlag.value = barModelValue1.value
-    const barValues = []
-    try {
-        for (let i = 0; i < barData.length; i++) {
-            const value = barData[i][barFlag.value]
-            barValues.push(value)
-        }
-        option.series[0].data = barValues
-        barChart.setOption(option)
-    } catch (error) {
-        console.log(error)
-    }
+    updateBarChart()
 }
 
 function changeBarTwo(newValue) {
     barModelValue2.value = newValue
-    barFlag2.value = barModelValue2.value
-    const barValues = []
-    try {
-        for (let i = 0; i < barData.length; i++) {
-            const value = barData[i][barFlag2.value]
-            barValues.push(value)
-        }
-        option.series[1].data = barValues
-        barChart.setOption(option)
-    } catch (error) {
-        console.log(error)
+    updateBarChart()
+}
+
+function updateBarChart() {
+    const barValues1 = barData.map(item => item[barModelValue1.value])
+    const barValues2 = barData.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+// 监听时间变化重新渲染表格
+watch(dateRange, async () => {
+    if (dateRange.value) {
+        loading.value = true
+        const resp = await setAdStructureData()
+        updatePieChartData(resp)
+        updateBarChartData(resp)
+        loading.value = false
     }
+})
+
+// 根据新数据和当前下拉框选择更新 饼图数据
+function updatePieChartData(resp) {
+    option2.series[0].data = [
+        { value: resp.pie_data[0][modelValue.value], name: '自动' },
+        { value: resp.pie_data[1][modelValue.value], name: '手动' },
+    ]
+    pieChart.setOption(option2)
 }
 
+// 根据新数据和当前下拉框选择更新 柱状图数据
+function updateBarChartData(resp) {
+    const barValues1 = resp.line_data.map(item => item[barModelValue1.value])
+    const barValues2 = resp.line_data.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+const computedBarOptions1 = computed(() => createDisabledOptions(barOptions1, barModelValue2.value, barModelValue1.value))
+const computedBarOptions2 = computed(() => createDisabledOptions(barOptions2, barModelValue1.value, barModelValue2.value))
+const computedPieOptions = computed(() => createDisabledOptions(pieOptions, modelValue.value))
+
 // 初始化图表
 function initChart() {
     // 柱状图配置
@@ -358,31 +368,32 @@ function initChart() {
             trigger: 'axis',
             axisPointer: {
                 type: 'shadow',
-                label: {
-                    backgroundColor: '#6a7985'
-                }
+            },
+            rich: {
+                b: {
+                    color: '#4C5058',
+                    fontSize: 15,
+                    fontWeight: 'bold',
+                    lineHeight: 33
+                },
             }
         },
-        // legend: {data: ['数据1', '数据2'],},
         toolbox: {
             feature: {
                 saveAsImage: { yAxisIndex: 'none' }
             }
         },
-        grid: {
-            top: 55, right: 60, bottom: 55, left: 55,
-
-        },
+        grid: { top: 55, right: 60, bottom: 55, left: 55, },
         xAxis: [
             {
                 type: 'category',
                 boundaryGap: true,
-                data: classificationMapList,
+                data: xAxisMapList,
                 axisLabel: {
                     rotate: -30, // 将标签旋转
                     fontSize: 13
-                },
-            },
+                }
+            }
         ],
         yAxis: [
             {
@@ -417,7 +428,7 @@ function initChart() {
         ],
         series: [
             {
-                // name: '数据1',
+                name: barOptionsMap[barModelValue1.value],
                 type: 'bar',
                 barWidth: '15%',
                 data: ACOSList,
@@ -432,7 +443,7 @@ function initChart() {
                 },
             },
             {
-                // name: '数据2',
+                name: barOptionsMap[barModelValue2.value],
                 type: 'bar',
                 barWidth: '15%',
                 data: SpendList,
@@ -457,7 +468,6 @@ function initChart() {
         },
         series: [
             {
-                // name: modelValue.value,
                 type: 'pie',
                 radius: ['20%', '45%'],
                 avoidLabelOverlap: false,
@@ -507,6 +517,8 @@ function resizeChart() {
     pieChart.resize()
 }
 
+defineExpose({ resizeChart })
+
 </script>
 
 <style scoped>

+ 208 - 200
src/views/adManage/sp/campaigns/chartComponents/dataTendency.vue

@@ -1,253 +1,261 @@
 <template>
-  <div v-loading="loading">
-    <MetricsCards v-model="metrics" :metric-items="metricsItems" @change="changeMetric"></MetricsCards>
-    <div style="height: 350px;" ref="chartRef"></div>
-</div>
+    <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, onBeforeMount, watch, computed } from 'vue'
+import {ref, onMounted, onBeforeUnmount, Ref, onBeforeMount, watch, computed} from 'vue'
 import * as echarts from 'echarts'
-import { useShopInfo } from '/@/stores/shopInfo'
-import { getLineData, getCardData } from '../api'
-import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {getLineData, getCardData} from '../api'
+import {spCampaignMetricsEnum} from '/@/views/adManage/utils/enum.js'
 import MetricsCards from '/@/components/MetricsCards/index.vue'
 import XEUtils from 'xe-utils'
-import { buildChartOpt } from '/@/views/adManage/utils/tools.js'
-import { usePublicData } from '/@/stores/publicData'
-import { storeToRefs } from 'pinia'
+import {buildChartOpt} from '/@/views/adManage/utils/tools.js'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
 
 defineOptions({
-  name: "DataTendencyChart"
+    name: 'DataTendencyChart'
 })
 
 onMounted(() => {
-  getMetricsItems()
-	addResize()
-  // initLine()
-  setTimeout(() => { initLine() }, 0)
+    getMetricsItems()
+    addResize()
+    // initLine()
+    setTimeout(() => {
+        initLine()
+    }, 0)
 })
 onBeforeUnmount(() => {
-	if(chartObj) {
-		chartObj.dispose()
-    chartObj = null
-	}
-  removeResize()
+    if (chartObj) {
+        chartObj.dispose()
+        chartObj = null
+    }
+    removeResize()
 })
 
 const publicData = usePublicData()
 const metrics = ref([
-  {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
-  {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
-  {metric: 'Spend', color: '#ff9500', 'label': '花费'},
+    {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
+    {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
+    {metric: 'Spend', color: '#ff9500', 'label': '花费'},
 ])
 const shopInfo = useShopInfo()
 const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj:any
+let chartObj: any
 const chartRef = ref()
 const option: any = {
-  dataset: {
-    source: []
-  },
-  tooltip: {
-    trigger: 'axis',
-    axisPointer: {
-      label: {
-        backgroundColor: '#6a7985'
-      }
-    }
-  },
-  legend: {
-    selected: {},  // 控制显隐
-    show: false
-  },
-  grid: {
-    top: 50, right: 150, bottom: 30, left: 55,
-  },
-  xAxis: {
-    type: 'category'
-  },
-  yAxis: [
-    {
-      id: 0,
-      type: 'value',
-      name: '曝光量',
-      splitLine: {
-        show: true // 设置显示分割线
-      },
-      axisLine: {
-        show: true,
-				lineStyle: {
-					color: '#0085ff'
-				}
-      },
-      show: true
+    dataset: {
+        source: []
     },
-    {
-      id: 1,
-      type: 'value',
-      name: '点击量',
-      position: 'right',
-      splitLine: {
-        show: false
-      },
-      axisLine: {
-        show: true,
-				lineStyle: {
-					color: '#3fd4cf'
-				}
-      },
-      show: true
+    tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+            label: {
+                backgroundColor: '#6a7985'
+            }
+        }
     },
-    {
-      id: 2,
-      type: 'value',
-      position: 'right',
-      offset: 90,
-      name: '花费',
-      splitLine: {
+    legend: {
+        selected: {},  // 控制显隐
         show: false
-      },
-      axisLine: {
-        show: true,
-				lineStyle: {
-					color: '#ff9500'
-				}
-      },
-      show: true
-    }
-  ],
-  series: [
-    {
-      id: 0,
-      name: '曝光量',
-      type: 'bar',
-      encode: {
-        x: 'date',
-        y: 'Impression'
-      },
-      barWidth: '20px',
-      yAxisIndex: 0,
-      itemStyle: {
-        color: '#0085ff',
-        borderRadius: [6, 6, 6, 6],
-      }
     },
-    {
-      id: 1,
-      name: '点击量',
-      type: 'line',
-      encode: {
-        x: 'date',
-        y: 'Click'
-      },
-      symbolSize: 6,
-      symbol: 'circle',
-      smooth: true,
-      yAxisIndex: 1,
-      itemStyle: { color: '#3fd4cf', borderColor: '#3fd4cf' },
-      areaStyle: {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: '#3fd4cf53' },
-          { offset: 1, color: '#3fd4cf03' },
-        ]),
-      },
-      emphasis: {
-        focus:'series'
-      }
+    grid: {
+        top: 50, right: 150, bottom: 30, left: 55,
     },
-    {
-      id: 2,
-      name: '花费',
-      type: 'line',
-      encode: {
-        x: 'date',
-        y: 'Spend'
-      },
-      symbolSize: 6,
-      symbol: 'circle',
-      smooth: true,
-      yAxisIndex: 2,
-      itemStyle: { color: '#ff9500', borderColor: '#ff9500' },
-      areaStyle: {
-        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-          { offset: 0, color: '#ff950053' },
-          { offset: 1, color: '#ff950003' },
-        ]),
-      },
-      emphasis: {
-        focus:'series'
-      }
-    }
-  ]
+    xAxis: {
+        type: 'category'
+    },
+    yAxis: [
+        {
+            id: 0,
+            type: 'value',
+            name: '曝光量',
+            splitLine: {
+                show: true // 设置显示分割线
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#0085ff'
+                }
+            },
+            show: true
+        },
+        {
+            id: 1,
+            type: 'value',
+            name: '点击量',
+            position: 'right',
+            splitLine: {
+                show: false
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#3fd4cf'
+                }
+            },
+            show: true
+        },
+        {
+            id: 2,
+            type: 'value',
+            position: 'right',
+            offset: 90,
+            name: '花费',
+            splitLine: {
+                show: false
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#ff9500'
+                }
+            },
+            show: true
+        }
+    ],
+    series: [
+        {
+            id: 0,
+            name: '曝光量',
+            type: 'bar',
+            encode: {
+                x: 'date',
+                y: 'Impression'
+            },
+            barWidth: '20px',
+            yAxisIndex: 0,
+            itemStyle: {
+                color: '#0085ff',
+                borderRadius: [6, 6, 6, 6],
+            }
+        },
+        {
+            id: 1,
+            name: '点击量',
+            type: 'line',
+            encode: {
+                x: 'date',
+                y: 'Click'
+            },
+            symbolSize: 6,
+            symbol: 'circle',
+            smooth: true,
+            yAxisIndex: 1,
+            itemStyle: {color: '#3fd4cf', borderColor: '#3fd4cf'},
+            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: 'date',
+                y: 'Spend'
+            },
+            symbolSize: 6,
+            symbol: 'circle',
+            smooth: true,
+            yAxisIndex: 2,
+            itemStyle: {color: '#ff9500', borderColor: '#ff9500'},
+            areaStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                    {offset: 0, color: '#ff950053'},
+                    {offset: 1, color: '#ff950003'},
+                ]),
+            },
+            emphasis: {
+                focus: 'series'
+            }
+        }
+    ]
 }
-const { dateRange } = storeToRefs(publicData)
+const {dateRange} = storeToRefs(publicData)
 const loading = ref(true)
 
 const getDataset = async () => {
-	const resp = await getLineData({profile: shopInfo.profile.profile_id, start: dateRange.value[0], end: dateRange.value[1]})
-	return resp.data
+    const resp = await getLineData({profile: shopInfo.profile.profile_id, start: dateRange.value[0], end: dateRange.value[1]})
+    return resp.data
 }
 const initLine = async () => {
-	chartObj = echarts.init(chartRef.value)
-	const items = await getDataset()
-	option.dataset.source = items
-  XEUtils.arrayEach(metricsItems.value, info => {
-    option.legend.selected[info.label] = false
-  })
-  for(const info of metrics.value) {
-    option.legend.selected[info.label] = true
-  }
-	chartObj.setOption(option)
-  loading.value = false
+    chartObj = echarts.init(chartRef.value)
+    const items = await getDataset()
+    option.dataset.source = items
+    XEUtils.arrayEach(metricsItems.value, info => {
+        option.legend.selected[info.label] = false
+    })
+    for (const info of metrics.value) {
+        option.legend.selected[info.label] = true
+    }
+    chartObj.setOption(option)
+    loading.value = false
 }
 
 const getMetricsItems = async () => {
-	const resp = await getCardData({start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id})
-	const data = resp.data
-  metricsItems.value.length = 0
-	XEUtils.arrayEach(spCampaignMetricsEnum, 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 resp = await getCardData({start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id})
+    const data = resp.data
+    metricsItems.value.length = 0
+    XEUtils.arrayEach(spCampaignMetricsEnum, 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 opt = buildChartOpt(option, metrics.value)
+    chartObj.setOption(opt)
 }
 
 watch(
-  dateRange, 
-  async () => {
-    loading.value = true
-    await getMetricsItems()
-    const items = await getDataset()
-    const opt = { dataset: { source: items } }
-    chartObj.setOption(opt)
-    loading.value = false
-  }
+        dateRange,
+        async () => {
+            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) }
+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%;
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    gap: 12px;
+    width: 100%;
 }
 </style>

+ 205 - 191
src/views/adManage/sp/campaigns/crud.tsx

@@ -1,8 +1,8 @@
 import * as api from './api'
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
-import { inject } from 'vue'
-import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
-import { dynBidStrategyEnum } from '/@/views/adManage/utils/enum.js'
+import {dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {dynBidStrategyEnum} from '/@/views/adManage/utils/enum.js'
 
 
 export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
@@ -23,193 +23,207 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 		return await api.AddObj(form);
 	};
 
-	//权限判定
-	const hasPermissions = inject('$hasPermissions');
+    //权限判定
+    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() {
+    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
-					}
-				},
-        campaignName: {
-          title: '广告活动',
-          column: {
-            width: '200px',
-						fixed: 'left'
-          },
-					search: {
-						show: true,
-						component: {
-							props: {
-								clearable: true
-							}
-						}
-					},
-					form: {
-						rules: [{required: true, message:'必填项'}]
-					}
-        },
-				targetingType: {
-					title: '投放类型',
-					type: 'dict-select',
-					search: {
-						show: true
-					},
-					dict: dict({
-						data: [
-							{ value: 'AUTO', label: '自动' },
-							{ value: 'MANUAL', label: '手动' },
-						]
-					})
-				},
-				state: {
-					title: '状态'
-				},
-				dynBidStrategy: {
-					title: '竞价策略',
-					form: {
-						show: false,
-					},
-					column: {
-						width: '160px'
-					},
-					type: 'dict-select',
-					dict: dict({
-						data: dynBidStrategyEnum
-					})
-				},
-				startDate: {
-					title: '开始日期',
-					column: {
-            width: '100px'
-          },
-				},
-				endDate: {
-					title: '结束日期',
-					column: {
-            width: '100px'
-          },
-				},
-				budget: {
-					title: '预算'
-				},
-				portfolioName: {
-					title: '广告组合'
-				},
-				suggestedBudget: {
-					title: '建议竞价',
-					form: {
-						show: false
-					}
-				},
-				percentTimeInBudget: {
-					title: '预算活跃均值',
-					column:{
-						minWidth: 150
-					},
-					form: {
-						show: false
-					}
-				},
-				MissedImpressions: {
-					title: '预计错过的曝光',
-					form: {
-						show: false
-					},
-					column:{
-						width: 180
-					},
-				},
-				MissedClicks: {
-					title: '预计错过的点击',
-					form: {
-						show: false
-					},
-					column:{
-						width: 180
-					},
-				},
-				MissedSales: {
-					title: '预计错过的销售',
-					form: {
-						show: false
-					},
-					column:{
-						width: 180
-					},
-				},
-        ...BaseColumn
-			}
-		}
-	}
+                        }
+                    }
+                }
+            },
+            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
+                    }
+                },
+                campaignName: {
+                    title: '广告活动',
+                    column: {
+                        width: '200px',
+                        fixed: 'left'
+                    },
+                    search: {
+                        show: true,
+                        component: {
+                            props: {
+                                clearable: true
+                            }
+                        }
+                    },
+                    form: {
+                        rules: [{required: true, message: '必填项'}]
+                    }
+                },
+                targetingType: {
+                    title: '投放类型',
+                    type: 'dict-select',
+                    search: {
+                        show: true
+                    },
+                    dict: dict({
+                        data: [
+                            {value: 'AUTO', label: '自动'},
+                            {value: 'MANUAL', label: '手动'},
+                        ]
+                    })
+                },
+                state: {
+                    title: '状态',
+                    column: {
+                        width: '90px',
+                        align: 'center'
+                    },
+                    type: 'dict-select',
+                    search: {
+                        show: true
+                    },
+                    dict: dict({
+                        data: [
+                            {value: 'PAUSED', label: '已暂停', color: 'warning'},
+                            {value: 'ENABLED', label: '投放中', color: 'success'},
+                        ]
+                    })
+                },
+                dynBidStrategy: {
+                    title: '竞价策略',
+                    form: {
+                        show: false,
+                    },
+                    column: {
+                        width: '160px'
+                    },
+                    type: 'dict-select',
+                    dict: dict({
+                        data: dynBidStrategyEnum
+                    })
+                },
+                startDate: {
+                    title: '开始日期',
+                    column: {
+                        width: '100px'
+                    },
+                },
+                endDate: {
+                    title: '结束日期',
+                    column: {
+                        width: '100px'
+                    },
+                },
+                budget: {
+                    title: '预算'
+                },
+                portfolioName: {
+                    title: '广告组合'
+                },
+                suggestedBudget: {
+                    title: '建议竞价',
+                    form: {
+                        show: false
+                    }
+                },
+                percentTimeInBudget: {
+                    title: '预算活跃均值',
+                    column: {
+                        minWidth: 150
+                    },
+                    form: {
+                        show: false
+                    }
+                },
+                MissedImpressions: {
+                    title: '预计错过的曝光',
+                    form: {
+                        show: false
+                    },
+                    column: {
+                        width: 180
+                    },
+                },
+                MissedClicks: {
+                    title: '预计错过的点击',
+                    form: {
+                        show: false
+                    },
+                    column: {
+                        width: 180
+                    },
+                },
+                MissedSales: {
+                    title: '预计错过的销售',
+                    form: {
+                        show: false
+                    },
+                    column: {
+                        width: 180
+                    },
+                },
+                ...BaseColumn
+            }
+        }
+    }
 }

+ 1 - 1
src/views/adManage/sp/campaigns/index.vue

@@ -91,7 +91,7 @@ const jumpGroup = (row: any) => {
 }
 
 watch(
-	[dateRange, profile], 
+	[dateRange, profile],
 	async () => {
 		crudExpose.doRefresh()
 	}

+ 4 - 4
src/views/adManage/sp/chartComponents/dataTendency.vue

@@ -149,8 +149,8 @@ const option: any = {
       symbol: 'circle',
       smooth: true,
       yAxisIndex: 1,
-      itemStyle: { 
-        // color: '#ff9500', 
+      itemStyle: {
+        // color: '#ff9500',
         // borderColor: '#ff9500'
       },
       areaStyle: {
@@ -233,7 +233,7 @@ const initLine = async () => {
     info.name = metrics.value[index].label
     info.axisLine.lineStyle.color = metrics.value[index].color
   })
-  
+
   XEUtils.arrayEach(metricsItems.value, info => {
     option.legend.selected[info.label] = false
   })
@@ -293,7 +293,7 @@ const changeStatDim = async () => {
 }
 
 watch(
-  queryParams, 
+  queryParams,
   async () => {
     loading.value = true
     await getMetricsItems()

+ 61 - 63
src/views/adManage/sp/keywords/chartComponents/adStruct.vue

@@ -4,9 +4,9 @@
             <el-col :span="24">
                 <div style="margin-left: 45%">
                     <span style="background: #3a83f7; width: 18px; height: 10px; margin-top: 8px; display: inline-block; border-radius: 3px;"></span>
-                    <TextSelector v-model="barModelValue1" :options="barOptions1" @change="changeBarOne" style="margin-top: 5px; margin-left: 8px;"/>
+                    <TextSelector v-model="barModelValue1" :options="computedBarOptions1" @change="changeBarOne" style="margin-top: 5px; margin-left: 8px;"/>
                     <span style="background: #f19a37; width: 18px; height: 10px; margin-top: 8px; margin-left: 20px; display: inline-block; border-radius: 3px;"></span>
-                    <TextSelector v-model="barModelValue2" :options="barOptions2" @change="changeBarTwo" style="margin-top: 5px; margin-left: 8px;"/>
+                    <TextSelector v-model="barModelValue2" :options="computedBarOptions2" @change="changeBarTwo" style="margin-top: 5px; margin-left: 8px;"/>
                 </div>
                 <div ref="bar" style="height: 400px;"></div>
             </el-col>
@@ -15,16 +15,22 @@
 </template>
 
 <script setup>
-import { onMounted, ref, inject } from "vue"
+import { onMounted, ref, inject, computed, watch } from "vue"
 import * as echarts from "echarts"
 import TextSelector from '/@/components/TextSelector/index.vue'
 import { getAdStructureData } from "/@/views/adManage/sp/keywords/api"
+import { createDisabledOptions } from '../../../utils/dropdowndisable'
+import { barOptionsMap } from "/@/views/adManage/utils/enum"
+import { useShopInfo } from '/@/stores/shopInfo'
 
+
+const shopInfo = useShopInfo()
 let barChart = ref()
+const pie = ref()
 const bar = ref()
 const loading = ref(true)
 
-let dateRange = inject('dateRange')
+const dateRange = inject('dateRange')
 
 // 下拉框相关
 const barOptions1 = [
@@ -202,32 +208,31 @@ onMounted(async () => {
 })
 
 // 获取总数据
-let resp = null
+let allData = null
 
 async function setAdStructureData() {
-    resp = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: '3006125408623189' })
-    console.log('resp.data', resp.data)
-    return resp.data
+    allData = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id })
+    return allData.data
 }
 
 // 饼图总数据和柱状图总数据
 let barData = null
-let allData = null
+let responseData = null
 // 柱状图初始数据
 let ACOSList
 let SpendList
-let matchTypeList
-let matchTypeMapList
+let xAxisList
+let xAxisMapList
 
 async function initBarData() {
-    allData = await setAdStructureData()
-    barData = allData
+    responseData = await setAdStructureData()
+    barData = responseData
     // 柱状图初始化数据
     ACOSList = barData.map(item => item.ACOS)
     SpendList = barData.map(item => item.Spend)
     // 将x轴映射为中文
-    matchTypeList = barData.map(item => item.matchType)
-    const matchTypeMap = {
+    xAxisList = barData.map(item => item.matchType)
+    const classificationMap = {
         'BROAD': '关键词-广泛',
         'category': '品类',
         'EXACT': '关键词-精准',
@@ -238,50 +243,58 @@ async function initBarData() {
         'substitutes': '同类商品',
         'complements': '关联商品'
     }
-    matchTypeMapList = matchTypeList.map(item => matchTypeMap[item])
+    xAxisMapList = xAxisList.map(item => classificationMap[item])
     loading.value = false
 }
 
 // 重置图像
-let barFlag = ref()
-let barFlag2 = ref()
+let flag = ref()
 let option
 let option2
 
+// 点击下拉框后重新渲染柱状图
 function changeBarOne(newValue) {
     barModelValue1.value = newValue
-    barFlag.value = barModelValue1.value
-    const barValues = []
-    try {
-        for (let i = 0; i < barData.length; i++) {
-            const value = barData[i][barFlag.value]
-            barValues.push(value)
-        }
-        // barChart.clear()
-        option.series[0].data = barValues
-        barChart.setOption(option)
-    } catch (error) {
-        console.log(error)
-    }
+    updateBarChart()
 }
 
 function changeBarTwo(newValue) {
     barModelValue2.value = newValue
-    barFlag2.value = barModelValue2.value
-    const barValues = []
-    try {
-        for (let i = 0; i < barData.length; i++) {
-            const value = barData[i][barFlag2.value]
-            barValues.push(value)
-        }
-        // barChart.clear()
-        option.series[1].data = barValues
-        barChart.setOption(option)
-    } catch (error) {
-        console.log(error)
+    updateBarChart()
+}
+
+function updateBarChart() {
+    const barValues1 = barData.map(item => item[barModelValue1.value])
+    const barValues2 = barData.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+// 监听时间变化重新渲染表格
+watch(dateRange, async () => {
+    if (dateRange.value) {
+        loading.value = true
+        const resp = await setAdStructureData()
+        updateBarChartData(resp)
+        loading.value = false
     }
+})
+
+// 根据新数据和当前下拉框选择更新 柱状图数据
+function updateBarChartData(resp) {
+    const barValues1 = resp.map(item => item[barModelValue1.value])
+    const barValues2 = resp.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
 }
 
+const computedBarOptions1 = computed(() => createDisabledOptions(barOptions1, barModelValue2.value, barModelValue1.value))
+const computedBarOptions2 = computed(() => createDisabledOptions(barOptions2, barModelValue1.value, barModelValue2.value))
+
 // 初始化图表
 function initChart() {
     // 柱状图配置
@@ -301,28 +314,17 @@ function initChart() {
                 saveAsImage: { yAxisIndex: 'none' }
             }
         },
-        grid: {
-            top: 50, right: 60, bottom: 50, left: 60,
-
-        },
+        grid: { top: 50, right: 60, bottom: 50, left: 60 },
         xAxis: [
             {
                 type: 'category',
                 boundaryGap: true,
-                data: matchTypeMapList,
-                // axisLabel: {
-                //     rotate: 30, // 将标签旋转45度
-                //     fontSize: 12
-                // },
+                data: xAxisMapList,
             },
         ],
         yAxis: [
             {
                 type: 'value',
-                // name: '数据1',
-                // axisLabel: {
-                //     formatter: '{value} %'
-                // },
                 axisLine: {
                     show: true,
                     lineStyle: {
@@ -332,13 +334,9 @@ function initChart() {
             },
             {
                 type: 'value',
-                // name: '数据2',
                 splitLine: {
                     show: false
                 },
-                // axisLabel: {
-                //     formatter: '{value} 单位2'
-                // },
                 axisLine: {
                     show: true,
                     lineStyle: {
@@ -349,7 +347,7 @@ function initChart() {
         ],
         series: [
             {
-                // name: '数据1',
+                name: barOptionsMap[barModelValue1.value],
                 type: 'bar',
                 barWidth: '3%',
                 data: ACOSList,
@@ -364,7 +362,7 @@ function initChart() {
                 },
             },
             {
-                // name: '数据2',
+                name: barOptionsMap[barModelValue2.value],
                 type: 'bar',
                 barWidth: '3%',
                 data: SpendList,
@@ -387,7 +385,7 @@ function initChart() {
 function resizeChart() {
     barChart.resize()
 }
-
+defineExpose({ resizeChart })
 </script>
 
 <style scoped>

+ 19 - 11
src/views/adManage/sp/keywords/crud.tsx

@@ -40,14 +40,6 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
                     add: {
                         show: false
                     },
-                    create: {
-                        text: '新建广告活动',
-                        type: 'primary',
-                        show: true,
-                        click() {
-
-                        }
-                    }
                 }
             },
             search: {
@@ -104,7 +96,8 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
                     title: '关键词',
                     column: {
                         width: '200px',
-                        fixed: 'left'
+                        fixed: 'left',
+                        align: 'center'
                     },
                     search: {
                         show: true,
@@ -116,13 +109,28 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
                     },
                 },
                 state: {
-                    title: '状态'
+                    title: '状态',
+                    column: {
+                        width: '90px',
+                        align: 'center'
+                    },
+                    type: 'dict-select',
+                    search: {
+                        show: true
+                    },
+                    dict: dict({
+                        data: [
+                            {value: 'PAUSED', label: '已暂停', color: 'warning'},
+                            {value: 'ENABLED', label: '投放中', color: 'success'},
+                        ]
+                    })
                 },
                 campaignName: {
                     title: '广告活动名称',
                     column: {
                         width: '200px',
-                        fixed: 'left'
+                        fixed: 'left',
+                        align: 'center'
                     },
                     search: {
                         show: true,

+ 52 - 28
src/views/adManage/sp/placement/api.ts

@@ -1,41 +1,65 @@
-import { request } from '/@/utils/service'
-import { UserPageQuery } from '@fast-crud/fast-crud'
+import {request} from '/@/utils/service'
+import {AddReq, DelReq, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
 
-export const apiPrefix = '/api/ad_manage/spcampaigns/'
+export const apiPrefix = '/api/ad_manage/sptargets/';
 export function GetList(query: UserPageQuery) {
-	return request({
-		url: apiPrefix,
-		method: 'get',
-		params: query,
-	})
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
 }
 export function GetObj(id: any) {
-	return request({
-		url: apiPrefix + id + '/',
-		method: 'get',
-	})
+    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 + 'total/',
-		method: 'GET',
-		params: query,
-	})
+    return request({
+        url: apiPrefix + "total/",
+        method: 'GET',
+        params: query
+    })
 }
 
 export function getLineData(query: UserPageQuery) {
-	return request({
-		url: apiPrefix + 'daily/',
-		method: 'GET',
-		params: query,
-	})
+    return request({
+        url: apiPrefix + "daily/",
+        method: 'GET',
+        params: query
+    })
 }
 
-export function getAdStructureData(query: UserPageQuery) {
-	return request({
-		url: apiPrefix + 'structure/',
-		method: 'GET',
-		params: query,
-	})
+export function getAdStructureData(query) {
+    return request({
+        url: apiPrefix + "structure/",
+        method: 'GET',
+        params: query
+    })
 }

+ 393 - 0
src/views/adManage/sp/placement/chartComponents/adStruct.vue

@@ -0,0 +1,393 @@
+<template>
+    <div v-loading="loading">
+        <el-row :gutter="5">
+            <el-col :span="24">
+                <div style="margin-left: 45%">
+                    <span style="background: #3a83f7; width: 18px; height: 10px; margin-top: 8px; display: inline-block; border-radius: 3px;"></span>
+                    <TextSelector v-model="barModelValue1" :options="computedBarOptions1" @change="changeBarOne" style="margin-top: 5px; margin-left: 8px;"/>
+                    <span style="background: #f19a37; width: 18px; height: 10px; margin-top: 8px; margin-left: 20px; display: inline-block; border-radius: 3px;"></span>
+                    <TextSelector v-model="barModelValue2" :options="computedBarOptions2" @change="changeBarTwo" style="margin-top: 5px; margin-left: 8px;"/>
+                </div>
+                <div ref="bar" style="height: 400px;"></div>
+            </el-col>
+        </el-row>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref, inject, computed, watch } from "vue"
+import * as echarts from "echarts"
+import TextSelector from '/@/components/TextSelector/index.vue'
+import { getAdStructureData } from "/@/views/adManage/sp/targets/api"
+import { createDisabledOptions } from '../../../utils/dropdowndisable'
+import { barOptionsMap } from "/@/views/adManage/utils/enum"
+import { useShopInfo } from '/@/stores/shopInfo'
+
+
+const shopInfo = useShopInfo()
+let barChart = ref()
+const pie = ref()
+const bar = ref()
+const loading = ref(true)
+
+const dateRange = inject('dateRange')
+
+// 下拉框相关
+const barOptions1 = [
+    {
+        value: 'ACOS',
+        label: 'ACOS',
+    },
+    {
+        value: 'ROAS',
+        label: 'ROAS',
+    },
+    {
+        value: 'Spend',
+        label: '花费',
+        units: '$',
+    },
+    {
+        value: 'TotalSales',
+        label: '销售额',
+    },
+    {
+        value: 'TotalPurchases',
+        label: '订单数',
+    },
+    {
+        value: 'TotalUnitOrdered',
+        label: '销量',
+    },
+    {
+        value: 'CPC',
+        label: '点击成本'
+    },
+    {
+        value: 'CPA',
+        label: '订单成本'
+    },
+    {
+        value: 'Impression',
+        label: '曝光量',
+    },
+    {
+        value: 'Click',
+        label: '点击量',
+    },
+    {
+        value: 'qwe',
+        label: '点击率'
+    },
+    {
+        value: '转化率',
+        label: '转化率'
+    },
+    {
+        value: 'TotalSalesSameSKU',
+        label: '推广商品销售额'
+    },
+    {
+        value: 'TotalSalesOtherSKU',
+        label: '其他商品销售额'
+    },
+    {
+        value: 'TotalPurchasesSameSKU',
+        label: '推广商品订单数'
+    },
+    {
+        value: 'TotalPurchasesOtherSKU',
+        label: '其他商品订单数'
+    },
+    {
+        value: 'TotalUnitOrderedSameSKU',
+        label: '推广商品销量'
+    },
+    {
+        value: 'TotalUnitOrderedOtherSKU',
+        label: '其他商品销量'
+    },
+    {
+        value: 'TopOfSearchImpressionShare',
+        label: '搜索结果顶部展示份额'
+    },
+]
+let barModelValue1 = ref(barOptions1[0].value)
+
+const barOptions2 = [
+    {
+        value: 'ACOS',
+        label: 'ACOS',
+    },
+    {
+        value: 'ROAS',
+        label: 'ROAS',
+    },
+    {
+        value: 'Spend',
+        label: '花费',
+        units: '$',
+    },
+    {
+        value: 'TotalSales',
+        label: '销售额',
+    },
+    {
+        value: 'TotalPurchases',
+        label: '订单数',
+    },
+    {
+        value: 'TotalUnitOrdered',
+        label: '销量',
+    },
+    {
+        value: 'CPC',
+        label: '点击成本'
+    },
+    {
+        value: 'CPA',
+        label: '订单成本'
+    },
+    {
+        value: 'Impression',
+        label: '曝光量',
+    },
+    {
+        value: 'Click',
+        label: '点击量',
+    },
+    {
+        value: 'qwe',
+        label: '点击率'
+    },
+    {
+        value: '转化率',
+        label: '转化率'
+    },
+    {
+        value: 'TotalSalesSameSKU',
+        label: '推广商品销售额'
+    },
+    {
+        value: 'TotalSalesOtherSKU',
+        label: '其他商品销售额'
+    },
+    {
+        value: 'TotalPurchasesSameSKU',
+        label: '推广商品订单数'
+    },
+    {
+        value: 'TotalPurchasesOtherSKU',
+        label: '其他商品订单数'
+    },
+    {
+        value: 'TotalUnitOrderedSameSKU',
+        label: '推广商品销量'
+    },
+    {
+        value: 'TotalUnitOrderedOtherSKU',
+        label: '其他商品销量'
+    },
+    {
+        value: 'TopOfSearchImpressionShare',
+        label: '搜索结果顶部展示份额'
+    },
+]
+let barModelValue2 = ref(barOptions2[2].value)
+
+onMounted(async () => {
+    barChart = echarts.init(bar.value)
+
+    window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
+    setTimeout(() => {
+        resizeChart()
+    }, 0)
+
+    await initBarData()
+    initChart()
+})
+
+// 获取总数据
+let allData = null
+
+async function setAdStructureData() {
+    allData = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id })
+    return allData.data
+}
+
+// 柱状图总数据
+let barData = null
+let responseData = null
+// 柱状图初始数据
+let ACOSList
+let SpendList
+let xAxisList
+let xAxisMapList
+
+async function initBarData() {
+    responseData = await setAdStructureData()
+    barData = responseData
+    // 柱状图初始化数据
+    ACOSList = barData.map(item => item.ACOS)
+    SpendList = barData.map(item => item.Spend)
+    // 将x轴映射为中文
+    xAxisList = barData.map(item => item.Classification)
+    const classificationMap = {
+        'BROAD': '关键词-广泛',
+        'category': '品类',
+        'EXACT': '关键词-精准',
+        'asin': '商品',
+        'PHRASE': '关键词-词组',
+        'close-match': '紧密匹配',
+        'loose-match': '广泛匹配',
+        'substitutes': '同类商品',
+        'complements': '关联商品'
+    }
+    xAxisMapList = xAxisList.map(item => classificationMap[item])
+    loading.value = false
+}
+
+// 重置图像
+let flag = ref()
+let option
+let option2
+
+// 点击下拉框后重新渲染柱状图
+function changeBarOne(newValue) {
+    barModelValue1.value = newValue
+    updateBarChart()
+}
+
+function changeBarTwo(newValue) {
+    barModelValue2.value = newValue
+    updateBarChart()
+}
+
+function updateBarChart() {
+    const barValues1 = barData.map(item => item[barModelValue1.value])
+    const barValues2 = barData.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+// 监听时间变化重新渲染表格
+watch(dateRange, async () => {
+    if (dateRange.value) {
+        loading.value = true
+        const resp = await setAdStructureData()
+        updateBarChartData(resp)
+        loading.value = false
+    }
+})
+
+// 根据新数据和当前下拉框选择更新 柱状图数据
+function updateBarChartData(resp) {
+    const barValues1 = resp.map(item => item[barModelValue1.value])
+    const barValues2 = resp.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+const computedBarOptions1 = computed(() => createDisabledOptions(barOptions1, barModelValue2.value, barModelValue1.value))
+const computedBarOptions2 = computed(() => createDisabledOptions(barOptions2, barModelValue1.value, barModelValue2.value))
+
+// 初始化图表
+function initChart() {
+    // 柱状图配置
+    option = {
+        tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+                type: 'shadow',
+                label: {
+                    backgroundColor: '#6a7985'
+                }
+            }
+        },
+        // legend: {data: ['数据1', '数据2'],},
+        toolbox: {
+            feature: {
+                saveAsImage: { yAxisIndex: 'none' }
+            }
+        },
+        grid: { top: 50, right: 60, bottom: 50, left: 60 },
+        xAxis: [
+            {
+                type: 'category',
+                boundaryGap: true,
+                data: xAxisMapList,
+            },
+        ],
+        yAxis: [
+            {
+                type: 'value',
+                axisLine: {
+                    show: true,
+                    lineStyle: {
+                        color: '#3a83f7' // 第一个 Y 轴的颜色
+                    }
+                }
+            },
+            {
+                type: 'value',
+                splitLine: {
+                    show: false
+                },
+                axisLine: {
+                    show: true,
+                    lineStyle: {
+                        color: '#f19a37' // 第一个 Y 轴的颜色
+                    }
+                }
+            }
+        ],
+        series: [
+            {
+                name: barOptionsMap[barModelValue1.value],
+                type: 'bar',
+                barWidth: '3%',
+                data: ACOSList,
+                yAxisIndex: 0,
+                itemStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        { offset: 0, color: '#3a83f7' },
+                        { offset: 1, color: 'rgb(111, 209, 206)' },
+                    ]),
+                    // 柱状图圆角
+                    borderRadius: [6, 6, 6, 6],
+                },
+            },
+            {
+                name: barOptionsMap[barModelValue2.value],
+                type: 'bar',
+                barWidth: '3%',
+                data: SpendList,
+                yAxisIndex: 1,
+                itemStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        { offset: 0, color: '#f19a37' },
+                        { offset: 1, color: 'rgb(234,207,135)' },
+                    ]),
+                    // 柱状图圆角
+                    borderRadius: [6, 6, 6, 6],
+                },
+            },
+        ],
+    }
+    barChart.setOption(option)
+    resizeChart()
+}
+
+function resizeChart() {
+    barChart.resize()
+}
+defineExpose({ resizeChart })
+</script>
+
+<style scoped>
+
+</style>

+ 261 - 0
src/views/adManage/sp/placement/chartComponents/dataTendency.vue

@@ -0,0 +1,261 @@
+<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, onBeforeMount, watch, computed} from 'vue'
+import * as echarts from 'echarts'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {getLineData, getCardData} from '../api'
+import {spCampaignMetricsEnum} from '/@/views/adManage/utils/enum.js'
+import MetricsCards from '/@/components/MetricsCards/index.vue'
+import XEUtils from 'xe-utils'
+import {buildChartOpt} from '/@/views/adManage/utils/tools.js'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
+
+defineOptions({
+    name: 'DataTendencyChart'
+})
+
+onMounted(() => {
+    getMetricsItems()
+    addResize()
+    // initLine()
+    setTimeout(() => {
+        initLine()
+    }, 0)
+})
+onBeforeUnmount(() => {
+    if (chartObj) {
+        chartObj.dispose()
+        chartObj = null
+    }
+    removeResize()
+})
+
+const publicData = usePublicData()
+const metrics = ref([
+    {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
+    {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
+    {metric: 'Spend', color: '#ff9500', 'label': '花费'},
+])
+const shopInfo = useShopInfo()
+const metricsItems: Ref<MetricData[]> = ref([])
+let chartObj: any
+const chartRef = ref()
+const option: any = {
+    dataset: {
+        source: []
+    },
+    tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+            label: {
+                backgroundColor: '#6a7985'
+            }
+        }
+    },
+    legend: {
+        selected: {},  // 控制显隐
+        show: false
+    },
+    grid: {
+        top: 50, right: 150, bottom: 30, left: 55,
+    },
+    xAxis: {
+        type: 'category'
+    },
+    yAxis: [
+        {
+            id: 0,
+            type: 'value',
+            name: '曝光量',
+            splitLine: {
+                show: true // 设置显示分割线
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#0085ff'
+                }
+            },
+            show: true
+        },
+        {
+            id: 1,
+            type: 'value',
+            name: '点击量',
+            position: 'right',
+            splitLine: {
+                show: false
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#3fd4cf'
+                }
+            },
+            show: true
+        },
+        {
+            id: 2,
+            type: 'value',
+            position: 'right',
+            offset: 90,
+            name: '花费',
+            splitLine: {
+                show: false
+            },
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color: '#ff9500'
+                }
+            },
+            show: true
+        }
+    ],
+    series: [
+        {
+            id: 0,
+            name: '曝光量',
+            type: 'bar',
+            encode: {
+                x: 'date',
+                y: 'Impression'
+            },
+            barWidth: '20px',
+            yAxisIndex: 0,
+            itemStyle: {
+                color: '#0085ff',
+                borderRadius: [6, 6, 6, 6],
+            }
+        },
+        {
+            id: 1,
+            name: '点击量',
+            type: 'line',
+            encode: {
+                x: 'date',
+                y: 'Click'
+            },
+            symbolSize: 6,
+            symbol: 'circle',
+            smooth: true,
+            yAxisIndex: 1,
+            itemStyle: {color: '#3fd4cf', borderColor: '#3fd4cf'},
+            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: 'date',
+                y: 'Spend'
+            },
+            symbolSize: 6,
+            symbol: 'circle',
+            smooth: true,
+            yAxisIndex: 2,
+            itemStyle: {color: '#ff9500', borderColor: '#ff9500'},
+            areaStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                    {offset: 0, color: '#ff950053'},
+                    {offset: 1, color: '#ff950003'},
+                ]),
+            },
+            emphasis: {
+                focus: 'series'
+            }
+        }
+    ]
+}
+const {dateRange} = storeToRefs(publicData)
+const loading = ref(true)
+
+const getDataset = async () => {
+    const resp = await getLineData({profile: shopInfo.profile.profile_id, start: dateRange.value[0], end: dateRange.value[1]})
+    return resp.data
+}
+const initLine = async () => {
+    chartObj = echarts.init(chartRef.value)
+    const items = await getDataset()
+    option.dataset.source = items
+    XEUtils.arrayEach(metricsItems.value, info => {
+        option.legend.selected[info.label] = false
+    })
+    for (const info of metrics.value) {
+        option.legend.selected[info.label] = true
+    }
+    chartObj.setOption(option)
+    loading.value = false
+}
+
+const getMetricsItems = async () => {
+    const resp = await getCardData({start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id})
+    const data = resp.data
+    metricsItems.value.length = 0
+    XEUtils.arrayEach(spCampaignMetricsEnum, 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)
+}
+
+watch(
+        dateRange,
+        async () => {
+            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%;
+}
+</style>

+ 155 - 86
src/views/adManage/sp/placement/crud.tsx

@@ -1,91 +1,160 @@
 import * as api from './api'
-import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
-import { inject } from 'vue'
-import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
-import { dynBidStrategyEnum } from '/@/views/adManage/utils/enum.js'
+import {AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
 
-export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const pageRequest = async (query: UserPageQuery) => {
-		query['profile'] = context['profileId']
-		return await api.GetList(query)
-	}
 
-	//权限判定
-	const hasPermissions = inject('$hasPermissions')
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+    const pageRequest = async (query: UserPageQuery) => {
+        query['profile'] = context['profileId']
+        query['start'] = context['start']
+        query['end'] = context['end']
+        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)
+    }
 
-	return {
-		crudOptions: {
-			table: {
-				height: 800,
-			},
-			container: {
-				fixedHeight: false,
-			},
-			actionbar: {
-				show: false
-			},
-			search: {
-				show: false,
-			},
-			toolbar: {
-				buttons: {
-					search: {
-						show: true,
-					},
-					compact: {
-						show: false,
-					},
-				},
-			},
-			request: {
-				pageRequest,
-			},
-			rowHandle: {
-				show: false,
-			},
-			columns: {
-				id: {
-					title: 'ID',
-					column: {
-						show: false,
-					},
-					form: {
-						show: false,
-					},
-				},
-				campaignName: {
-					title: '广告活动',
-					column: {
-						width: '200px',
-						fixed: 'left',
-					},
-					search: {
-						show: true,
-						component: {
-							props: {
-								clearable: true,
-							},
-						},
-					},
-				},
-				placement: {
-					title: '广告位',
-				},
-				dynBidStrategy: {
-					title: '竞价策略',
-					form: {
-						show: false,
-					},
-					column: {
-						width: '160px',
-					},
-					type: 'dict-select',
-					dict: dict({
-						data: dynBidStrategyEnum,
-					}),
-				},
-				...BaseColumn,
-			},
-		},
-	}
+    //权限判定
+    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: {
+                        iconRight: 'Delete',
+                        type: 'text',
+                        text: null
+                        // show: hasPermissions('dictionary:Delete'),
+                    },
+                },
+            },
+            columns: {
+                id: {
+                    title: 'ID',
+                    column: {
+                        show: false
+                    },
+                    form: {
+                        show: false
+                    }
+                },
+                campaignName: {
+                    title: '广告活动',
+                    column: {
+                        width: '200px',
+                        fixed: 'left',
+                        align: 'center',
+                    },
+                    search: {
+                        show: true,
+                        component: {
+                            props: {
+                                clearable: true
+                            }
+                        }
+                    },
+                    form: {
+                        rules: [{required: true, message: '必填项'}]
+                    }
+                },
+                placementClassification: {
+                    title: '广告位',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                dynBidStrategy: {
+                    title: '竞价策略',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                '竞价调整': {},
+                Impression: {
+                    title: '曝光量',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                Click: {
+                    title: '点击量',
+                    column: {
+                        align: 'center',
+                    },
+                },
+                CTR: {title: '点击率', column: {align: 'center'}},
+                Spend: {title: '花费', column: {align: 'center'}},
+                CPC: {title: '点击成本', column: {align: 'center'}},
+                TotalPurchases: {title: '订单数', column: {align: 'center'}},
+                TotalSales: {title: '销售额', column: {align: 'center'}},
+                TotalUnitOrdered: {title: '销量', column: {align: 'center'}},
+                PurchasesRate: {title: '转化率', column: {align: 'center'}},
+                ACOS: {title: 'ACOS', column: {align: 'center'}},
+                ROAS: {title: 'ROAS', column: {align: 'center'}},
+                CPA: {title: '订单成本', column: {align: 'center'}},
+                ...BaseColumn
+            }
+        }
+    }
 }

+ 78 - 27
src/views/adManage/sp/placement/index.vue

@@ -1,38 +1,89 @@
 <template>
-	<fs-page class="fs-page-custom">
-		<fs-crud ref="crudRef" v-bind="crudBinding">
-			<template #header-middle>
-				<el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
-					<el-tab-pane label="数据趋势" name="dataTendency">
-						<DataTendencyChart v-if="tabActiveName === 'dataTendency'" :fetchCard="getCardData" :fetchLine="getLineData" />
-					</el-tab-pane>
-					<el-tab-pane label="广告结构" name="adStruct">
-						<AdStructChart v-if="tabActiveName === 'adStruct'" />
-					</el-tab-pane>
-					<el-tab-pane label="散点视图" name="scatterView">
-						<div v-if="tabActiveName === 'adStruct'">散点视图</div>
-					</el-tab-pane>
-				</el-tabs>
-			</template>
-		</fs-crud>
-	</fs-page>
+    <fs-page class="fs-page-custom">
+        <fs-crud ref="crudRef" v-bind="crudBinding">
+            <template #header-middle>
+                <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card" @tab-change="changeTab">
+                    <el-tab-pane label="数据趋势" name="dataTendency">
+                        <DataTendencyChart ref="dataTendencyRef"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="广告结构" name="adStruct" :lazy="true">
+                        <AdStructChart ref="adStructChartRef"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点视图" name="scatterView" :lazy="true"></el-tab-pane>
+                </el-tabs>
+            </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>
+        </fs-crud>
+    </fs-page>
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted } from 'vue'
-import { useFs, FsPage } from '@fast-crud/fast-crud'
-import { createCrudOptions } from './crud'
-import DataTendencyChart from '/@/views/adManage/sp/chartComponents/dataTendency.vue'
-import { getCardData, getLineData } from './api'
-import { useShopInfo } from '/@/stores/shopInfo'
+import {inject, nextTick, onMounted, ref} from 'vue'
+import {FsPage, useFs} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useRoute, useRouter} from 'vue-router'
+import DataTendencyChart from './chartComponents/dataTendency.vue'
+import {useShopInfo} from '/@/stores/shopInfo'
+import AdStructChart from './chartComponents/adStruct.vue'
 
 const tabActiveName = ref('dataTendency')
 const shopInfo = useShopInfo()
-const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { profileId: shopInfo.profile.profile_id } })
+const dateRange = inject('dateRange')
+const start = dateRange.value[0]
+const end = dateRange.value[1]
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: {profileId: shopInfo.profile.profile_id, start: start, end: end}})
 
-onMounted(async () => {
-	crudExpose.doRefresh()
+const route = useRoute()
+const router = useRouter()
+const adStructChartRef = ref()
+const dataTendencyRef = ref()
+
+
+onMounted(() => {
+    crudExpose.doRefresh()
 })
+
+const resizeTabChart = () => {
+    if (tabActiveName.value === 'dataTendency') {
+        dataTendencyRef.value.resizeChart()
+    } else if (tabActiveName.value === 'adStruct') {
+        adStructChartRef.value.resizeChart()
+    }
+}
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
+}
+defineExpose({resizeTabChart})
+
+const jumpGroup = (row: any) => {
+    router.push({
+        name: 'CampaignDetail',
+        query: {id: row.id, campaignId: row.campaignId, tagsViewName: row.campaignName},
+    })
+}
+
 </script>
 
-<style scoped></style>
+<style lang="scss">
+.chart-tabs {
+    margin: 5px 0;
+
+    .el-tabs__nav {
+        padding-left: 0 !important;
+    }
+}
+
+</style>

+ 65 - 72
src/views/adManage/sp/targets/chartComponents/adStruct.vue

@@ -15,17 +15,22 @@
 </template>
 
 <script setup>
-import { onMounted, ref, inject, computed } from "vue"
+import { onMounted, ref, inject, computed, watch } from "vue"
 import * as echarts from "echarts"
 import TextSelector from '/@/components/TextSelector/index.vue'
 import { getAdStructureData } from "/@/views/adManage/sp/targets/api"
-import { createDisabledOptions } from '../utils/dropdowndisable'
+import { createDisabledOptions } from '../../../utils/dropdowndisable'
+import { barOptionsMap } from "/@/views/adManage/utils/enum"
+import { useShopInfo } from '/@/stores/shopInfo'
 
+
+const shopInfo = useShopInfo()
 let barChart = ref()
+const pie = ref()
 const bar = ref()
 const loading = ref(true)
 
-let dateRange = inject('dateRange')
+const dateRange = inject('dateRange')
 
 // 下拉框相关
 const barOptions1 = [
@@ -203,91 +208,92 @@ onMounted(async () => {
 })
 
 // 获取总数据
-let resp = null
+let allData = null
 
 async function setAdStructureData() {
-    resp = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: '3006125408623189' })
-    console.log('resp.data', resp.data)
-    return resp.data
+    allData = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id })
+    return allData.data
 }
 
-// 饼图总数据和柱状图总数据
+// 柱状图总数据
 let barData = null
-let allData = null
+let responseData = null
 // 柱状图初始数据
 let ACOSList
 let SpendList
-let classificationList
-let classificationMapList
+let xAxisList
+let xAxisMapList
 
 async function initBarData() {
-    allData = await setAdStructureData()
-    barData = allData
-    console.log('barData', barData)
+    responseData = await setAdStructureData()
+    barData = responseData
     // 柱状图初始化数据
     ACOSList = barData.map(item => item.ACOS)
     SpendList = barData.map(item => item.Spend)
     // 将x轴映射为中文
-    classificationList = barData.map(item => item.Classification)
+    xAxisList = barData.map(item => item.Classification)
     const classificationMap = {
+        'BROAD': '关键词-广泛',
         'category': '品类',
+        'EXACT': '关键词-精准',
         'asin': '商品',
+        'PHRASE': '关键词-词组',
+        'close-match': '紧密匹配',
+        'loose-match': '广泛匹配',
+        'substitutes': '同类商品',
+        'complements': '关联商品'
     }
-    classificationMapList = classificationList.map(item => classificationMap[item])
+    xAxisMapList = xAxisList.map(item => classificationMap[item])
     loading.value = false
 }
 
 // 重置图像
-let barFlag = ref()
-let barFlag2 = ref()
+let flag = ref()
 let option
 let option2
 
+// 点击下拉框后重新渲染柱状图
 function changeBarOne(newValue) {
     barModelValue1.value = newValue
-    barFlag.value = barModelValue1.value
-    console.log(111, barModelValue1.value)
-    console.log(222, barModelValue2.value)
-    const barValues = []
-    try {
-        for (let i = 0; i < barData.length; i++) {
-            const value = barData[i][barFlag.value]
-            barValues.push(value)
-        }
-        option.series[0].data = barValues
-        barChart.setOption(option)
-    } catch (error) {
-        console.log(error)
-    }
+    updateBarChart()
 }
 
 function changeBarTwo(newValue) {
     barModelValue2.value = newValue
-    barFlag2.value = barModelValue2.value
-    const barValues = []
-    try {
-        for (let i = 0; i < barData.length; i++) {
-            const value = barData[i][barFlag2.value]
-            barValues.push(value)
-        }
-        // barChart.clear()
-        option.series[1].data = barValues
-        barChart.setOption(option)
-    } catch (error) {
-        console.log(error)
-    }
+    updateBarChart()
 }
 
-// 判断barOptions哪个选项应该被禁用
-const computedBarOptions1 = computed(() => createDisabledOptions(barOptions1, barModelValue2.value))
-const computedBarOptions2 = computed(() => createDisabledOptions(barOptions2, barModelValue1.value))
+function updateBarChart() {
+    const barValues1 = barData.map(item => item[barModelValue1.value])
+    const barValues2 = barData.map(item => item[barModelValue2.value])
 
-// const computedBarOptions2 = computed(() => {
-//     return barOptions2.map(option => ({
-//         ...option,
-//         disabled: barModelValue1.value === option.value
-//     }))
-// })
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+// 监听时间变化重新渲染表格
+watch(dateRange, async () => {
+    if (dateRange.value) {
+        loading.value = true
+        const resp = await setAdStructureData()
+        updateBarChartData(resp)
+        loading.value = false
+    }
+})
+
+// 根据新数据和当前下拉框选择更新 柱状图数据
+function updateBarChartData(resp) {
+    const barValues1 = resp.map(item => item[barModelValue1.value])
+    const barValues2 = resp.map(item => item[barModelValue2.value])
+
+    option.series[0].data = barValues1
+    option.series[1].data = barValues2
+    barChart.setOption(option)
+}
+
+const computedBarOptions1 = computed(() => createDisabledOptions(barOptions1, barModelValue2.value, barModelValue1.value))
+const computedBarOptions2 = computed(() => createDisabledOptions(barOptions2, barModelValue1.value, barModelValue2.value))
 
 // 初始化图表
 function initChart() {
@@ -302,30 +308,23 @@ function initChart() {
                 }
             }
         },
+        // legend: {data: ['数据1', '数据2'],},
         toolbox: {
             feature: {
                 saveAsImage: { yAxisIndex: 'none' }
             }
         },
-        grid: {
-            top: 50, right: 60, bottom: 50, left: 60,
-
-        },
+        grid: { top: 50, right: 60, bottom: 50, left: 60 },
         xAxis: [
             {
                 type: 'category',
                 boundaryGap: true,
-                data: classificationMapList,
-                // axisLabel: {
-                //     rotate: 30, // 将标签旋转45度
-                //     fontSize: 12
-                // },
+                data: xAxisMapList,
             },
         ],
         yAxis: [
             {
                 type: 'value',
-                // name: '数据1',
                 axisLine: {
                     show: true,
                     lineStyle: {
@@ -335,13 +334,9 @@ function initChart() {
             },
             {
                 type: 'value',
-                // name: '数据2',
                 splitLine: {
                     show: false
                 },
-                // axisLabel: {
-                //     formatter: '{value} 单位2'
-                // },
                 axisLine: {
                     show: true,
                     lineStyle: {
@@ -352,7 +347,7 @@ function initChart() {
         ],
         series: [
             {
-                // name: '数据1',
+                name: barOptionsMap[barModelValue1.value],
                 type: 'bar',
                 barWidth: '3%',
                 data: ACOSList,
@@ -367,7 +362,7 @@ function initChart() {
                 },
             },
             {
-                // name: '数据2',
+                name: barOptionsMap[barModelValue2.value],
                 type: 'bar',
                 barWidth: '3%',
                 data: SpendList,
@@ -390,9 +385,7 @@ function initChart() {
 function resizeChart() {
     barChart.resize()
 }
-
 defineExpose({ resizeChart })
-
 </script>
 
 <style scoped>

+ 61 - 33
src/views/adManage/sp/targets/chartComponents/dataTendency.vue

@@ -1,37 +1,43 @@
 <template>
-    <MetricsCards v-model="metrics" :metric-items="metricsItems" @change="changeMetric"></MetricsCards>
-    <div style="height: 350px;" ref="chartRef"></div>
+    <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, onBeforeMount, watch, computed } from 'vue'
+import {ref, onMounted, onBeforeUnmount, Ref, onBeforeMount, watch, computed} from 'vue'
 import * as echarts from 'echarts'
-import { useShopInfo } from '/@/stores/shopInfo'
-import { getLineData, getCardData } from '../api'
-import { spCampaignMetricsEnum } from '/@/views/adManage/utils/enum.js'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {getLineData, getCardData} from '../api'
+import {spCampaignMetricsEnum} from '/@/views/adManage/utils/enum.js'
 import MetricsCards from '/@/components/MetricsCards/index.vue'
 import XEUtils from 'xe-utils'
-import { buildChartOpt } from '/@/views/adManage/utils/tools.js'
+import {buildChartOpt} from '/@/views/adManage/utils/tools.js'
+import {usePublicData} from '/@/stores/publicData'
+import {storeToRefs} from 'pinia'
 
 defineOptions({
-    name: "DataTendencyChart"
+    name: 'DataTendencyChart'
 })
 
-onBeforeMount(async () => {
-    await getMetricsItems()
-})
 onMounted(() => {
-    initLine()
+    getMetricsItems()
     addResize()
-});
+    // initLine()
+    setTimeout(() => {
+        initLine()
+    }, 0)
+})
 onBeforeUnmount(() => {
-    if(chartObj) {
+    if (chartObj) {
         chartObj.dispose()
         chartObj = null
     }
     removeResize()
 })
 
+const publicData = usePublicData()
 const metrics = ref([
     {metric: 'Impression', color: '#0085ff', 'label': '曝光量'},
     {metric: 'Click', color: '#3fd4cf', 'label': '点击量'},
@@ -39,7 +45,7 @@ const metrics = ref([
 ])
 const shopInfo = useShopInfo()
 const metricsItems: Ref<MetricData[]> = ref([])
-let chartObj:any
+let chartObj: any
 const chartRef = ref()
 const option: any = {
     dataset: {
@@ -141,15 +147,15 @@ const option: any = {
             symbol: 'circle',
             smooth: true,
             yAxisIndex: 1,
-            itemStyle: { color: '#3fd4cf', borderColor: '#3fd4cf' },
+            itemStyle: {color: '#3fd4cf', borderColor: '#3fd4cf'},
             areaStyle: {
                 color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-                    { offset: 0, color: '#3fd4cf53' },
-                    { offset: 1, color: '#3fd4cf03' },
+                    {offset: 0, color: '#3fd4cf53'},
+                    {offset: 1, color: '#3fd4cf03'},
                 ]),
             },
             emphasis: {
-                focus:'series'
+                focus: 'series'
             }
         },
         {
@@ -164,47 +170,53 @@ const option: any = {
             symbol: 'circle',
             smooth: true,
             yAxisIndex: 2,
-            itemStyle: { color: '#ff9500', borderColor: '#ff9500' },
+            itemStyle: {color: '#ff9500', borderColor: '#ff9500'},
             areaStyle: {
                 color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-                    { offset: 0, color: '#ff950053' },
-                    { offset: 1, color: '#ff950003' },
+                    {offset: 0, color: '#ff950053'},
+                    {offset: 1, color: '#ff950003'},
                 ]),
             },
             emphasis: {
-                focus:'series'
+                focus: 'series'
             }
         }
     ]
 }
+const {dateRange} = storeToRefs(publicData)
+const loading = ref(true)
+
 const getDataset = async () => {
-    const resp = await getLineData({profile: shopInfo.profile.profile_id, start: '2023-11-01', end: '2023-11-04'})
+    const resp = await getLineData({profile: shopInfo.profile.profile_id, start: dateRange.value[0], end: dateRange.value[1]})
     return resp.data
 }
 const initLine = async () => {
     chartObj = echarts.init(chartRef.value)
     const items = await getDataset()
     option.dataset.source = items
-    for(const info of metrics.value) {
+    XEUtils.arrayEach(metricsItems.value, info => {
+        option.legend.selected[info.label] = false
+    })
+    for (const info of metrics.value) {
         option.legend.selected[info.label] = true
     }
     chartObj.setOption(option)
+    loading.value = false
 }
 
 const getMetricsItems = async () => {
-    const resp = await getCardData({start: '2023-11-01', end: '2023-11-04', profile: shopInfo.profile.profile_id})
+    const resp = await getCardData({start: dateRange.value[0], end: dateRange.value[1], profile: shopInfo.profile.profile_id})
     const data = resp.data
+    metricsItems.value.length = 0
     XEUtils.arrayEach(spCampaignMetricsEnum, info => {
-        const tmp:MetricData = {
+        const tmp: MetricData = {
             label: info.label,
             value: info.value,
             metricVal: data[info.value],
             gapVal: data[`gap${info.value}`],
             preVal: data[`prev${info.value}`],
-            disabled: info.initShow ? true : false
         }
         metricsItems.value.push(tmp)
-        option.legend.selected[info.label] = false
     })
 }
 
@@ -213,12 +225,28 @@ const changeMetric = () => {
     chartObj.setOption(opt)
 }
 
-const resizeChart = () => { chartObj.resize() }
-const addResize = () => { window.addEventListener('resize', resizeChart) }
-const removeResize = () => { window.removeEventListener('resize', resizeChart) }
+watch(
+        dateRange,
+        async () => {
+            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)
+}
 
-defineExpose({resizeChart})
 
 </script>
 

+ 36 - 33
src/views/adManage/sp/targets/crud.tsx

@@ -40,14 +40,6 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
                     add: {
                         show: false
                     },
-                    create: {
-                        text: '新建广告活动',
-                        type: 'primary',
-                        show: true,
-                        click() {
-
-                        }
-                    }
                 }
             },
             search: {
@@ -100,28 +92,29 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
                         show: false
                     }
                 },
-                keywordText: {
-                    title: '关键词',
+                '商品和分类': {title: '商品和分类'},
+                state: {
+                    title: '状态',
                     column: {
-                        width: '200px',
-                        fixed: 'left'
+                        width: '90px',
+                        align: 'center'
                     },
+                    type: 'dict-select',
                     search: {
-                        show: true,
-                        component: {
-                            props: {
-                                clearable: true
-                            }
-                        }
+                        show: true
                     },
-                },
-                state: {
-                    title: '状态'
+                    dict: dict({
+                        data: [
+                            {value: 'PAUSED', label: '已暂停', color: 'warning'},
+                            {value: 'ENABLED', label: '投放中', color: 'success'},
+                        ]
+                    })
                 },
                 campaignName: {
                     title: '广告活动名称',
                     column: {
                         width: '200px',
+                        align: 'center',
                         fixed: 'left'
                     },
                     search: {
@@ -138,28 +131,38 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
                 },
                 adGroupName: {
                     title: '广告组名称',
+                    column: {
+                        align: 'center',
+                    },
                 },
                 suggestedBudget: {
                     title: '建议竞价',
-                    form: {
-                        show: false
-                    }
+                    column: {
+                        align: 'center',
+                    },
+                },
+                bid: {
+                    title: '出价',
+                    column: {
+                        align: 'center',
+                    },
                 },
-                bid: {title: '出价'},
                 '标签': {},
                 Impression: {
-                    title: '曝光量'
+                    title: '曝光量',
+                    column: {
+                        align: 'center',
+                    },
                 },
                 '搜索结果顶部展示份额': {},
                 Click: {
-                    title: '点击量'
-                },
-                CTR: {
-                    title: '点击率'
-                },
-                Spend: {
-                    title: '花费'
+                    title: '点击量',
+                    column: {
+                        align: 'center',
+                    },
                 },
+                CTR: {title: '点击率'},
+                Spend: {title: '花费'},
                 CPC: {title: '点击成本'},
                 TotalPurchases: {title: '订单数'},
                 TotalSales: {title: '销售额'},

+ 0 - 1
src/views/adManage/sp/targets/index.vue

@@ -37,7 +37,6 @@ const router = useRouter()
 const adStructChartRef = ref()
 const dataTendencyRef = ref()
 
-console.log(111, dateRange.value)
 
 onMounted(() => {
     crudExpose.doRefresh()

+ 0 - 6
src/views/adManage/sp/targets/utils/dropdowndisable.js

@@ -1,6 +0,0 @@
-export function createDisabledOptions(options, disabledValue) {
-    return options.map(option => ({
-        ...option,
-        disabled: option.value === disabledValue
-    }))
-}

+ 6 - 0
src/views/adManage/utils/dropdowndisable.ts

@@ -0,0 +1,6 @@
+export function createDisabledOptions(options, disabledValue, disableValue2) {
+    return options.map(option => ({
+        ...option,
+        disabled: option.value === disabledValue || option.value=== disableValue2
+    }))
+}

+ 53 - 21
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,7 +29,39 @@ 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 metricMap = {
+  'Spend': '花费',
+  'TotalSales': '销售额',
+  'TotalPurchases': '订单数',
+  'TotalUnitOrdered': '销量',
+  'Impression': '曝光量',
+  'Click': '点击量',
+}
+
+export const barOptionsMap = {
+    'ACOS': 'ACOS',
+    'ROAS': 'ROAS',
+    'Spend': '花费',
+    'TotalSales': '销售额',
+    'TotalPurchases': '订单数',
+    'TotalUnitOrdered': '销量',
+    'CPC': '点击成本',
+    'CPA': '订单成本',
+    'Impression': '曝光量',
+    'Click': '点击量',
+    'CTR': '点击率',
+    'PurchasesRate': '转化率',
+    'TotalSalesSameSKU': '推广商品销售额',
+    'TotalSalesOtherSKU': '其他商品销售额',
+    'TotalPurchasesSameSKU': '推广商品订单数',
+    'TotalPurchasesOtherSKU': '其他商品订单数',
+    'TotalUnitOrderedSameSKU': '推广商品销量',
+    'TotalUnitOrderedOtherSKU': '其他商品销量',
+    'TopOfSearchImpressionShare': '搜索结果顶部展示份额'
+}
+