Browse Source

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

guojing_wu 1 năm trước cách đây
mục cha
commit
5158f8eb4a
29 tập tin đã thay đổi với 2188 bổ sung468 xóa
  1. 6 2
      package-lock.json
  2. 1 0
      package.json
  3. 1 1
      src/components/DateRangePicker/index.vue
  4. 1 1
      src/components/MetricsCards/mCard.vue
  5. 1 1
      src/components/TextSelector/index.vue
  6. 164 0
      src/components/echartsComponents/BarChart.vue
  7. 182 179
      src/components/echartsComponents/BarLineChart.vue
  8. 46 31
      src/components/echartsComponents/PieBarChart.vue
  9. 42 0
      src/views/adManage/sb/adPlacement/api.ts
  10. 114 0
      src/views/adManage/sb/adPlacement/crud.tsx
  11. 112 0
      src/views/adManage/sb/adPlacement/index.vue
  12. 0 1
      src/views/adManage/sb/campaigns/crud.tsx
  13. 49 53
      src/views/adManage/sb/campaigns/index.vue
  14. 47 7
      src/views/adManage/sb/index.vue
  15. 42 0
      src/views/adManage/sb/keywords/api.ts
  16. 114 0
      src/views/adManage/sb/keywords/crud.tsx
  17. 116 0
      src/views/adManage/sb/keywords/index.vue
  18. 42 0
      src/views/adManage/sb/searchTerms/api.ts
  19. 114 0
      src/views/adManage/sb/searchTerms/crud.tsx
  20. 112 0
      src/views/adManage/sb/searchTerms/index.vue
  21. 8 0
      src/views/adManage/sp/campaigns/api.ts
  22. 2 2
      src/views/adManage/sp/campaigns/campaignDetail/index.vue
  23. 501 41
      src/views/adManage/sp/campaigns/chartComponents/adStruct.vue
  24. 3 2
      src/views/adManage/sp/campaigns/index.vue
  25. 38 39
      src/views/adManage/sp/index.vue
  26. 108 108
      src/views/adManage/sp/keywords/crud.tsx
  27. 42 0
      src/views/authorization/api.ts
  28. 150 0
      src/views/authorization/crud.tsx
  29. 30 0
      src/views/authorization/index.vue

+ 6 - 2
package-lock.json

@@ -38,6 +38,7 @@
 				"pinia-plugin-persist": "^1.0.0",
 				"postcss": "^8.4.21",
 				"print-js": "^1.6.0",
+				"process": "^0.11.10",
 				"qrcodejs2-fixes": "^0.0.2",
 				"qs": "^6.11.0",
 				"screenfull": "^6.0.2",
@@ -7401,7 +7402,8 @@
 		},
 		"node_modules/process": {
 			"version": "0.11.10",
-			"license": "MIT",
+			"resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
+			"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
 			"engines": {
 				"node": ">= 0.6.0"
 			}
@@ -13906,7 +13908,9 @@
 			"version": "1.6.0"
 		},
 		"process": {
-			"version": "0.11.10"
+			"version": "0.11.10",
+			"resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
+			"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
 		},
 		"process-nextick-args": {
 			"version": "2.0.1"

+ 1 - 0
package.json

@@ -38,6 +38,7 @@
 		"pinia-plugin-persist": "^1.0.0",
 		"postcss": "^8.4.21",
 		"print-js": "^1.6.0",
+		"process": "^0.11.10",
 		"qrcodejs2-fixes": "^0.0.2",
 		"qs": "^6.11.0",
 		"screenfull": "^6.0.2",

+ 1 - 1
src/components/DateRangePicker/index.vue

@@ -116,4 +116,4 @@ const shortcuts = [
 
 <style scoped>
 
-</style>
+</style>

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

@@ -30,7 +30,7 @@ interface Props {
 const props = defineProps<Props>()
 const emits = defineEmits(["update:modelValue", "change-metric"])
 const metric = ref(props.modelValue)
-const changeMetric = (oldVal: string, newVal: string) => {
+const changeMetric = ( newVal: string, oldVal: string) => {
   emits('update:modelValue', newVal)
   emits('change-metric', oldVal, newVal)
 }

+ 1 - 1
src/components/TextSelector/index.vue

@@ -34,7 +34,7 @@ const handleCommand = (command: string) => {
   const oldVal = data.value
   data.value = command
   emits('update:modelValue', command)
-  emits('change', oldVal, command)
+  emits('change', command, oldVal)
 }
 
 </script>

+ 164 - 0
src/components/echartsComponents/BarChart.vue

@@ -0,0 +1,164 @@
+<template>
+    <div style="margin-left: 45%">
+        <span style="background: rgb(176,234,232); width: 18px; height: 10px; margin-top: 8px; display: inline-block; border-radius: 3px;"></span>
+        <TextSelector :modelValue="modelValue" :options="options" style="margin-top: 5px; margin-left: 8px;"/>
+        <span style="background: rgb(234,207,135); width: 18px; height: 10px; margin-top: 8px; margin-left: 20px; display: inline-block; border-radius: 3px;"></span>
+        <TextSelector :modelValue="modelValue" :options="options" style="margin-top: 5px; margin-left: 8px;"/>
+    </div>
+    <div ref="barRef" style="height: 400px;"></div>
+</template>
+
+<script lang="ts" setup>
+import {ref, onMounted, onBeforeUnmount} from 'vue'
+import * as echarts from 'echarts'
+import TextSelector from '/@/components/TextSelector/index.vue'
+
+// defineOptions({
+//     name: 'DataTendencyChart'
+// })
+
+let chartObj: any
+const barRef = ref()
+
+function resizeChart() {
+    chartObj.resize()
+}
+
+function addResize() {
+    window.addEventListener('resize', resizeChart)
+}
+
+function removeResize() {
+    window.removeEventListener('resize', resizeChart)
+}
+
+onMounted(() => {
+    initLine()
+    addResize()
+})
+onBeforeUnmount(() => {
+    if (chartObj) {
+        chartObj.dispose()
+        chartObj = null
+    }
+    removeResize()
+})
+
+function initLine() {
+    chartObj = echarts.init(barRef.value)
+    const option = {
+        tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+                type: 'cross',
+                label: {
+                    backgroundColor: '#6a7985'
+                }
+            }
+        },
+        toolbox: {
+            feature: {
+                saveAsImage: {yAxisIndex: 'none'}
+            }
+        },
+        grid: {
+            top: 70, right: 60, bottom: 30, left: 55,
+
+        },
+        xAxis: [
+            {
+                type: 'category',
+                boundaryGap: true,
+                data: ['商品', '品类', '关键词-精准', '关键词-广泛', '关键词-词组'],
+            }
+        ],
+        yAxis: [
+            {
+                type: 'value',
+                name: '数据1',
+                axisLabel: {
+                    formatter: '{value} 单位1'
+                },
+                axisLine: {
+                    show: true
+                }
+            },
+            {
+                type: 'value',
+                name: '数据2',
+                splitLine: {
+                    show: false
+                },
+                axisLabel: {
+                    formatter: '{value} 单位2'
+                },
+                axisLine: {
+                    show: true
+                }
+            }
+        ],
+        series: [
+            {
+                name: '数据1',
+                type: 'bar',
+                // tooltip: {
+                //   valueFormatter: function (value) {
+                //     return value + ' ml';
+                //   }
+                // },
+                barWidth: '30%',
+                data: [15, 24, 21, 26, 34],
+                yAxisIndex: 0,
+                itemStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: 'rgba(111, 209, 206, 0.8)'},
+                        {offset: 1, color: 'rgba(111, 209, 206, 0.1)'},
+                    ]),
+                    //柱状图圆角
+                    borderRadius: [15, 15, 0, 0],
+                },
+            },
+            {
+                name: '数据2',
+                type: 'bar',
+                barWidth: '30%',
+                data: [10, 16, 28, 21, 30],
+                yAxisIndex: 1,
+                itemStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: '#ebb14d'},
+                        {offset: 1, color: 'rgba(111, 209, 206, 0.1)'},
+                    ]),
+                    //柱状图圆角
+                    borderRadius: [15, 15, 0, 0],
+                },
+            },
+        ],
+    }
+    chartObj.setOption(option)
+}
+
+defineExpose({resizeChart})
+
+// 下拉框相关
+const options = [
+    {
+        value: '花费',
+        label: '花费',
+    },
+    {
+        value: 'Option2',
+        label: 'Option2',
+    },
+    {
+        value: 'Option3',
+        label: 'Option3',
+    }
+]
+const modelValue = ref(options[0].value)
+
+</script>
+
+<style scoped>
+
+</style>

+ 182 - 179
src/components/echartsComponents/BarLineChart.vue

@@ -1,199 +1,202 @@
 <template>
-    <div id="barLine" ref="barLine" style="height: 400px;"></div>
-
+    <div ref="barLine" style="height: 400px;"></div>
 </template>
 
-<script>
-import { onMounted, onBeforeUnmount, ref } from 'vue'
+<script setup>
+import {onMounted, onBeforeUnmount, ref} from 'vue'
 import * as echarts from 'echarts'
 
-export default {
-    props: ['barLineData'],
-
-    setup(props) {
-        let lineChart = ref()
+const props = defineProps({
+    barLineData: {type: Object}
+})
+let lineChart = ref()
+const barLine = ref()
 
-        onMounted(() => {
-            lineChart = echarts.init(document.getElementById('barLine'))
-            updateChart()
-            window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
-            setTimeout(() => {
-                resizeChart()
-            },0)
-        })
-        onBeforeUnmount(() => {
-            window.removeEventListener('resize', resizeChart)   // 在组件销毁前移除事件监听,避免内存泄漏
-        })
-        // 获取图像数据 lieChartData
-        let chartData = props.barLineData
+onMounted(() => {
+    lineChart = echarts.init(barLine.value)
+    updateChart()
+    addResize()  // 监听窗口大小变化,调整图表大小
+    setTimeout(() => {
+        resizeChart()
+    }, 0)
+})
+onBeforeUnmount(() => {
+    if (lineChart) {
+        lineChart.dispose()
+        lineChart = null
+    }
+    removeResize()   // 在组件销毁前移除事件监听,避免内存泄漏
+})
+// 获取图像数据 lieChartData
+let chartData = props.barLineData
 
-        function updateChart() {
-            const option = {
-                // title: {
-                //   text: chartData.title,
-                // },
-                tooltip: {
-                    trigger: 'axis',
-                    axisPointer: {
-                        type: 'cross',
-                        label: {
-                            backgroundColor: '#6a7985'
-                        }
-                    }
+function updateChart() {
+    const option = {
+        tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+                type: 'cross',
+                label: {
+                    backgroundColor: '#6a7985'
+                }
+            }
+        },
+        // legend: {data: ['数据1', '数据2'], right: '50%',left: '50%'},
+        // toolbox: {
+        //     feature: {
+        //         saveAsImage: { yAxisIndex: 'none' }
+        //     }
+        // },
+        grid: {
+            top: 50, right: 150, bottom: 30, left: 55,
+        },
+        xAxis: [
+            {
+                type: 'category',
+                boundaryGap: true,
+                data: chartData.xData,
+            }
+        ],
+        yAxis: [
+            {
+                type: 'value',
+                name: 'ACOS',
+                splitLine: {
+                    show: true // 设置显示分割线
+                },
+                axisLabel: {
+                    formatter: '{value} 单位1'
+                },
+                axisLine: {
+                    show: true
+                }
+            },
+            {
+                type: 'value',
+                name: '点击率',
+                position: 'right',
+                splitLine: {
+                    show: false
+                },
+                axisLabel: {
+                    formatter: '{value} 单位2'
+                },
+                axisLine: {
+                    show: true
+                }
+            },
+            {
+                type: 'value',
+                position: 'right',
+                offset: 80,
+                name: '订单数',
+                splitLine: {
+                    show: false
                 },
-                // legend: {data: ['数据1', '数据2'], right: '50%',left: '50%'},
-                // toolbox: {
-                //     feature: {
-                //         saveAsImage: { yAxisIndex: 'none' }
-                //     }
+                axisLabel: {
+                    formatter: '{value} 单位3'
+                },
+                axisLine: {
+                    show: true
+                }
+            }
+        ],
+        series: [
+            {
+                name: '柱状图',
+                type: 'bar',
+                // tooltip: {
+                //   valueFormatter: function (value) {
+                //     return value + ' ml';
+                //   }
                 // },
-                grid: {
-                    top: 50, right: 150, bottom: 30, left: 55,
-
+                barWidth: '30%',
+                data: chartData.barData,
+                yAxisIndex: 0,
+                itemStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: 'rgba(111, 209, 206, 0.8)'},
+                        {offset: 1, color: 'rgba(111, 209, 206, 0.1)'},
+                    ]),
+                    //柱状图圆角
+                    borderRadius: [15, 15, 0, 0],
                 },
-                xAxis: [
-                    {
-                        type: 'category',
-                        boundaryGap: true,
-                        data: chartData.xData,
-                    }
-                ],
-                yAxis: [
-                    {
-                        type: 'value',
-                        name: 'ACOS',
-                        splitLine: {
-                            show: true // 设置显示分割线
-                        },
-                        axisLabel: {
-                            formatter: '{value} 单位1'
-                        },
-                        axisLine: {
-                            show: true
-                        }
-                    },
-                    {
-                        type: 'value',
-                        name: '点击率',
-                        position: 'right',
-                        splitLine: {
-                            show: false
-                        },
-                        axisLabel: {
-                            formatter: '{value} 单位2'
-                        },
-                        axisLine: {
-                            show: true
-                        }
-                    },
-                    {
-                        type: 'value',
-                        position: 'right',
-                        offset: 80,
-                        name: '订单数',
-                        splitLine: {
-                            show: false
-                        },
-                        axisLabel: {
-                            formatter: '{value} 单位3'
-                        },
-                        axisLine: {
-                            show: true
-                        }
-                    }
-                ],
-                series: [
-                    {
-                        name: '柱状图',
-                        type: 'bar',
-                        // tooltip: {
-                        //   valueFormatter: function (value) {
-                        //     return value + ' ml';
-                        //   }
-                        // },
-                        barWidth: '30%',
-                        data: chartData.barData,
-                        yAxisIndex: 0,
-                        itemStyle: {
-                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-                                { offset: 0, color: 'rgba(111, 209, 206, 0.8)' },
-                                { offset: 1, color: 'rgba(111, 209, 206, 0.1)' },
-                            ]),
-                            //柱状图圆角
-                            borderRadius: [15, 15, 0, 0],
-                        },
-                    },
-                    {
-                        name: '数据1',
-                        type: 'line',
-                        symbolSize: 6,
-                        symbol: 'circle',
-                        smooth: true,
-                        data: chartData.yData1,
-                        yAxisIndex: 1,
-                        lineStyle: { color: '#fe9a8b' },
-                        itemStyle: { color: '#fe9a8b', borderColor: '#fe9a8b' },
-                        areaStyle: {
-                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-                                { offset: 0, color: '#fe9a8bb3' },
-                                { offset: 1, color: '#fe9a8b03' },
-                            ]),
-                        },
-                    },
-                    {
-                        name: '数据2',
-                        type: 'line',
-                        symbolSize: 6,
-                        symbol: 'circle',
-                        smooth: true,
-                        data: chartData.yData2,
-                        yAxisIndex: 2,
-                        lineStyle: { color: '#9E87FF' },
-                        itemStyle: { color: '#9E87FF', borderColor: '#9E87FF' },
-                        areaStyle: {
-                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-                                { offset: 0, color: '#9E87FFb3' },
-                                { offset: 1, color: '#9E87FF03' },
-                            ]),
-                        },
-                        emphasis: {
-                            itemStyle: {
-                                color: {
-                                    type: 'radial',
-                                    x: 0.5,
-                                    y: 0.5,
-                                    r: 0.5,
-                                    colorStops: [
-                                        { offset: 0, color: '#9E87FF' },
-                                        { offset: 0.4, color: '#9E87FF' },
-                                        { offset: 0.5, color: '#fff' },
-                                        { offset: 0.7, color: '#fff' },
-                                        { offset: 0.8, color: '#fff' },
-                                        { offset: 1, color: '#fff' },
-                                    ],
-                                },
-                                borderColor: '#9E87FF',
-                                borderWidth: 2,
-                            },
+            },
+            {
+                name: '数据1',
+                type: 'line',
+                symbolSize: 6,
+                symbol: 'circle',
+                smooth: true,
+                data: chartData.yData1,
+                yAxisIndex: 1,
+                lineStyle: {color: '#fe9a8b'},
+                itemStyle: {color: '#fe9a8b', borderColor: '#fe9a8b'},
+                areaStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: '#fe9a8bb3'},
+                        {offset: 1, color: '#fe9a8b03'},
+                    ]),
+                },
+            },
+            {
+                name: '数据2',
+                type: 'line',
+                symbolSize: 6,
+                symbol: 'circle',
+                smooth: true,
+                data: chartData.yData2,
+                yAxisIndex: 2,
+                lineStyle: {color: '#9E87FF'},
+                itemStyle: {color: '#9E87FF', borderColor: '#9E87FF'},
+                areaStyle: {
+                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                        {offset: 0, color: '#9E87FFb3'},
+                        {offset: 1, color: '#9E87FF03'},
+                    ]),
+                },
+                emphasis: {
+                    itemStyle: {
+                        color: {
+                            type: 'radial',
+                            x: 0.5,
+                            y: 0.5,
+                            r: 0.5,
+                            colorStops: [
+                                {offset: 0, color: '#9E87FF'},
+                                {offset: 0.4, color: '#9E87FF'},
+                                {offset: 0.5, color: '#fff'},
+                                {offset: 0.7, color: '#fff'},
+                                {offset: 0.8, color: '#fff'},
+                                {offset: 1, color: '#fff'},
+                            ],
                         },
+                        borderColor: '#9E87FF',
+                        borderWidth: 2,
                     },
-                ],
-            }
-            lineChart.setOption(option)
-        }
+                },
+            },
+        ],
+    }
+    lineChart.setOption(option)
+}
 
-        // 自适应调整窗口
-        function resizeChart() {
-            if (lineChart) {
-                lineChart.resize()
-            }
-        }
+// 自适应调整窗口
+function resizeChart() {
+    if (lineChart) {
+        lineChart.resize()
+    }
+}
 
-        return {resizeChart}
-    },
+function addResize() {
+    window.addEventListener('resize', resizeChart)
+}
 
+function removeResize() {
+    window.removeEventListener('resize', resizeChart)
 }
 
+defineExpose({resizeChart, updateChart})
 
 </script>
 

+ 46 - 31
src/components/echartsComponents/PieBarChart.vue

@@ -2,15 +2,21 @@
     <div>
         <el-row :gutter="5">
             <el-col :span="8">
-                <div style="display: flex;">
+                <div>
                     <!--<span>{{ modelValue }}</span>-->
                     <TextSelector :modelValue="modelValue" :options="options" style="margin-top: 5px"/>
                 </div>
 
-                <div id="pie" style="height: 400px;"></div>
+                <div ref="pie" style="height: 400px;"></div>
             </el-col>
             <el-col :span="16">
-                <div id="bar" style="height: 400px;"></div>
+                <div style="margin-left: 40%">
+                    <span style="background: rgb(176,234,232); width: 18px; height: 10px; margin-top: 8px; display: inline-block; border-radius: 3px;"></span>
+                    <TextSelector :modelValue="modelValue" :options="options" style="margin-top: 5px; margin-left: 8px;"/>
+                    <span style="background: rgb(234,207,135); width: 18px; height: 10px; margin-top: 8px; margin-left: 20px; display: inline-block; border-radius: 3px;"></span>
+                    <TextSelector :modelValue="modelValue" :options="options" style="margin-top: 5px; margin-left: 8px;"/>
+                </div>
+                <div ref="bar" style="height: 400px;"></div>
             </el-col>
         </el-row>
     </div>
@@ -21,18 +27,25 @@ import { onMounted, ref } from "vue"
 import * as echarts from "echarts"
 import TextSelector from '/@/components/TextSelector/index.vue'
 
+let props = defineProps({
+    pieBarChartData: {
+        type: Object,
+    }
+})
+
 let pieChart = ref()
 let barChart = ref()
+const pie = ref()
+const bar = ref()
 
 onMounted(() => {
-    pieChart = echarts.init(document.getElementById('pie'))
-    barChart = echarts.init(document.getElementById('bar'))
+    barChart = echarts.init(bar.value)
+    pieChart = echarts.init(pie.value)
     setChartData()
     window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
     setTimeout(() => {
         resizeChart()
     }, 0)
-
 })
 
 function setChartData() {
@@ -47,7 +60,7 @@ function setChartData() {
                 }
             }
         },
-        legend: {data: ['数据1', '数据2'],},
+        // legend: {data: ['数据1', '数据2'],},
         toolbox: {
             feature: {
                 saveAsImage: { yAxisIndex: 'none' }
@@ -99,14 +112,14 @@ function setChartData() {
                 //   }
                 // },
                 barWidth: '30%',
-                data: [2, 24, 21, 40, 51],
+                data: props.pieBarChartData.barData[0],
                 yAxisIndex: 0,
                 itemStyle: {
                     color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                         { offset: 0, color: 'rgba(111, 209, 206, 0.8)' },
                         { offset: 1, color: 'rgba(111, 209, 206, 0.1)' },
                     ]),
-                    //柱状图圆角
+                    // 柱状图圆角
                     borderRadius: [15, 15, 0, 0],
                 },
             },
@@ -114,14 +127,14 @@ function setChartData() {
                 name: '数据2',
                 type: 'bar',
                 barWidth: '30%',
-                data: [10, 20, 30, 40, 50],
+                data: props.pieBarChartData.barData[1],
                 yAxisIndex: 1,
                 itemStyle: {
                     color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                         { offset: 0, color: '#ebb14d' },
                         { offset: 1, color: 'rgba(111, 209, 206, 0.1)' },
                     ]),
-                    //柱状图圆角
+                    // 柱状图圆角
                     borderRadius: [15, 15, 0, 0],
                 },
             },
@@ -131,35 +144,38 @@ function setChartData() {
     // 饼图配置
     const option2 = {
         tooltip: {
-            trigger: 'item'
+            trigger: 'item',
         },
         series: [
             {
                 name: 'Access From',
                 type: 'pie',
-                radius: ['40%', '70%'],
+                radius: ['25%', '50%'],
                 avoidLabelOverlap: false,
-                label: {
-                    show: false,
-                    position: 'center'
+                itemStyle: {
+                    borderWidth: 1, // 设置边框的宽度
+                    borderColor: '#fff', // 将边框颜色设置为白色或图表背景颜色
                 },
                 emphasis: {
                     label: {
                         show: true,
-                        fontSize: 40,
+                        // fontSize: 40,
                         fontWeight: 'bold'
                     }
                 },
+                label: {
+                    normal: {
+                        show: true,
+                        position: 'outside',
+                        // formatter: '{b}: {c} ({d}%)' // b为数据名,c为数据值,d为百分比
+                    }
+                },
                 labelLine: {
-                    show: false
+                    normal: {
+                        show: true
+                    }
                 },
-                data: [
-                    { value: 1048, name: 'Search Engine' },
-                    { value: 735, name: 'Direct' },
-                    { value: 580, name: 'Email' },
-                    { value: 484, name: 'Union Ads' },
-                    { value: 300, name: 'Video Ads' }
-                ]
+                data: props.pieBarChartData.pieData
             }
         ]
     }
@@ -168,10 +184,8 @@ function setChartData() {
 }
 
 function resizeChart() {
-    if (barChart) {
-        barChart.resize()
-        pieChart.resize()
-    }
+    barChart.resize()
+    pieChart.resize()
 }
 
 defineExpose({ resizeChart })
@@ -179,8 +193,8 @@ defineExpose({ resizeChart })
 // 下拉框相关
 const options = [
     {
-        value: 'Option1',
-        label: 'Option1',
+        value: '花费',
+        label: '花费',
     },
     {
         value: 'Option2',
@@ -192,6 +206,7 @@ const options = [
     }
 ]
 const modelValue = ref(options[0].value)
+
 </script>
 
 <style scoped>

+ 42 - 0
src/views/adManage/sb/adPlacement/api.ts

@@ -0,0 +1,42 @@
+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/spkeywords/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: InfoReq) {
+    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 },
+    });
+}

+ 114 - 0
src/views/adManage/sb/adPlacement/crud.tsx

@@ -0,0 +1,114 @@
+import * as api from './api'
+import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject, nextTick, ref } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+
+
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const pageRequest = async (query: UserPageQuery) => {
+		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: 600
+			},
+			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: {
+        keywordText: {
+          title: '关键词'
+        },
+				matchType: {
+					title: '匹配类型',
+					type: 'dict-select',
+					search: {
+						show: true
+					},
+					dict: dict({
+						data: [
+							{ value: 'BROAD', label: '广泛匹配' },
+							{ value: 'PHRASE', label: '词组匹配' },
+							{ value: 'EXACT', label: '精准匹配' },
+						]
+					})
+				},
+				state: {
+					title: '状态'
+				},
+				bid: {
+					title: '出价'
+				},
+				"campaign": {
+					title: '广告活动'
+				},
+				"adGroup": {
+					title: '广告组'
+				}
+			}
+		}
+	}
+}

+ 112 - 0
src/views/adManage/sb/adPlacement/index.vue

@@ -0,0 +1,112 @@
+<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="barLine">
+                        <MetricsCards v-model="metrics" :metric-items="options" @change="changeMetric"></MetricsCards>
+                        <div style="display: flex; justify-content: flex-end">
+                            <el-button-group class="ml-4" style="margin-top: 10px; margin-right: 30px">
+                                <el-button type="primary" @click="changeChartData">日</el-button>
+                                <el-button type="primary" @click="changeChartData">周</el-button>
+                                <el-button type="primary" @click="changeChartData">月</el-button>
+                            </el-button-group>
+                        </div>
+                        <BarLineChart ref="barLineRef" :barLineData="barLineData"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点视图" name="scatterView" :lazy="true"></el-tab-pane>
+                </el-tabs>
+            </template>
+        </fs-crud>
+    </fs-page>
+</template>
+
+<script setup>
+import {ref, onMounted, onBeforeUnmount, watch, nextTick, onActivated, reactive} 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 AdStructChart from '/@/views/adManage/sp/campaigns/chartComponents/adStruct.vue'
+import DataTendencyChart from '/@/views/adManage/sp/campaigns/chartComponents/dataTendency.vue'
+import BarLineChart from '/@/components/echartsComponents/BarLineChart.vue'
+import PieBarChart from '/@/components/echartsComponents/PieBarChart.vue'
+import BarChart from "/@/components/echartsComponents/BarChart.vue"
+
+const tabActiveName = ref('barLine')
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: {}})
+
+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 route = useRoute()
+const router = useRouter()
+const barLineRef = ref()
+
+const barLineData = reactive({
+    xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+    barData: [12, 13.4, 12.5, 16, 14.5, 15.6, 12.3],
+    yData1: [18, 13, 10, 8, 9, 10, 14.2],
+    yData2: [14, 15, 12, 16, 15, 13, 14.5]
+})
+
+onMounted(() => {
+    crudExpose.doRefresh()
+})
+
+const resizeTabChart = () => {
+    if (tabActiveName.value === 'barLine') {
+        barLineRef.value.resizeChart()
+    } else if (tabActiveName.value === 'bar') {
+        // BarChartRef.value.resizeChart()
+    }
+}
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
+}
+const changeMetric = () => {
+    console.log(metrics.value)
+}
+
+// todo 发送请求获取数据切换图表
+function changeChartData() {
+    updateData({
+        xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+        barData: [10, 13.4, 12, 14, 20, 14, 11.1],
+        yData1: [7, 2, 4, 8, 4, 15, 10],
+        yData2: [15, 10, 12, 14, 12, 10, 12.5]
+    });
+}
+
+function updateData(newData) {
+    // 更新响应式数据
+    Object.assign(barLineData, newData)
+    // 然后更新图表
+    barLineRef.value.updateChart()
+}
+
+defineExpose({resizeTabChart})
+
+</script>
+
+<style lang="scss">
+.chart-tabs {
+    margin: 5px 0;
+
+    .el-tabs__nav {
+        padding-left: 0 !important;
+    }
+}
+
+</style>

+ 0 - 1
src/views/adManage/sb/campaigns/crud.tsx

@@ -7,7 +7,6 @@ import {useRouter} from 'vue-router'
 export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
     const pageRequest = async (query: UserPageQuery) => {
         return await api.GetList(query)
-
     }
     const editRequest = async ({form, row}: EditReq) => {
         form.id = row.id

+ 49 - 53
src/views/adManage/sb/campaigns/index.vue

@@ -2,22 +2,24 @@
     <fs-page class="fs-page-custom">
         <fs-crud ref="crudRef" v-bind="crudBinding">
             <template #header-middle>
-                <el-tabs type="border-card" @click="clickFn" class="chart-tabs">
+                <el-tabs type="border-card" @click="changeTab" class="chart-tabs">
                     <el-tab-pane label="数据趋势">
                         <MetricsCards v-model="metrics" :metric-items="options" @change="changeMetric"></MetricsCards>
                         <div style="display: flex; justify-content: flex-end">
                             <el-button-group class="ml-4" style="margin-top: 10px; margin-right: 30px">
-                                <el-button type="primary">日</el-button>
-                                <el-button type="primary">周</el-button>
-                                <el-button type="primary">月</el-button>
+                                <el-button type="primary" @click="changeChartData">日</el-button>
+                                <el-button type="primary" @click="changeChartData">周</el-button>
+                                <el-button type="primary" @click="changeChartData">月</el-button>
                             </el-button-group>
                         </div>
                         <BarLineChart ref="barLine" :barLineData="barLineData"/>
                     </el-tab-pane>
                     <el-tab-pane label="广告结构">
-                        <PieBarChart ref="pieBar"/>
+                        <PieBarChart ref="pieBar" :pieBarChartData="pieBarChartData"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点透视">
+                        <!--<ScatterChart ref="scatter"/>-->
                     </el-tab-pane>
-                    <el-tab-pane label="散点透视"><ScatterChart ref="scatter"/></el-tab-pane>
                 </el-tabs>
             </template>
         </fs-crud>
@@ -25,7 +27,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted, reactive } from 'vue'
+import { ref, onMounted, inject, nextTick } from 'vue'
 import { useFs, FsPage } from '@fast-crud/fast-crud'
 import { createCrudOptions } from './crud'
 import BarLineChart from '/@/components/echartsComponents/BarLineChart.vue'
@@ -35,69 +37,63 @@ import MetricsCards from "/@/components/MetricsCards/index.vue"
 
 const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} })
 
-let barLineData = reactive({
-    xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
-    barData: [12, 13.4, 12.5, 16, 14.5, 15.6, 12.3],
-    yData1: [5, 6, 7, 8, 9, 10, 11],
-    yData2: [14, 15, 12, 16, 15, 13, 14.5]
-})
-
-let scatterData = [
-    [10.0, 8.04],
-    [8.07, 6.95],
-    [13.0, 7.58],
-    [9.05, 8.81],
-    [11.0, 8.33],
-    [14.0, 7.66],
-    [13.4, 6.81],
-    [10.0, 6.33],
-    [14.0, 8.96],
-    [12.5, 6.82],
-    [9.15, 7.2],
-    [11.5, 7.2],
-    [3.03, 4.23],
-    [12.2, 7.83],
-    [2.02, 4.47],
-    [1.05, 3.33],
-    [4.05, 4.96],
-    [6.03, 7.24],
-    [12.0, 6.26],
-    [12.0, 8.84],
-    [7.08, 5.82],
-    [5.02, 5.68]
-]
-
 onMounted(() => {
     crudExpose.doRefresh()
 })
 
-const pieBar = ref(null)
-const barLine = ref(null)
-const scatter = ref(null)
+let barLineData= inject('barLineData')
+let pieBarChartData = inject('pieBarChartData')
+const pieBar = ref()
+const barLine = ref()
+const scatter = ref()
 
-function clickFn() {
+function resizeTabChart() {
     pieBar.value.resizeChart()
     barLine.value.resizeChart()
-    scatter.value.resizeChart()
+}
+
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
 }
 
 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%' },
+    {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%'},
 ])
 
 function changeMetric() {
     console.log(metrics.value)
 }
 
+// todo 发送请求获取数据切换图表
+function changeChartData() {
+    updateData({
+        xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+        barData: [10, 13.4, 12, 14, 20, 14, 11.1],
+        yData1: [7, 2, 4, 8, 4, 15, 10],
+        yData2: [15, 10, 12, 14, 12, 10, 12.5]
+    });
+}
+
+function updateData(newData) {
+    // 更新响应式数据
+    Object.assign(barLineData, newData)
+    // 然后更新图表
+    barLine.value.updateChart()
+}
+
+defineExpose({ resizeTabChart })
+
 </script>
 
 <style scoped>

+ 47 - 7
src/views/adManage/sb/index.vue

@@ -5,14 +5,21 @@
             <el-select v-model="portfolios" placeholder="广告组合"></el-select>
         </div>
         <div>
-            <el-tabs class="asj-tabs">
+            <el-tabs class="asj-tabs" @tab-change="changeTab">
                 <el-tab-pane label="广告活动">
-                    <campaigns></campaigns>
+                    <campaigns ref="campaignsRef"/>
+                </el-tab-pane>
+                <el-tab-pane label="关键词" :lazy="true">
+                    <!--<keywords ref="keywordsRef"/>-->
+                </el-tab-pane>
+                <el-tab-pane label="商品投放" :lazy="true">
+
+                </el-tab-pane>
+                <el-tab-pane label="搜索词" :lazy="true">
+                    <!--<SearchTerm ref="searchTermRef"/>-->
+                </el-tab-pane>
+                <el-tab-pane label="广告位" :lazy="true">
                 </el-tab-pane>
-                <el-tab-pane label="关键词" ></el-tab-pane>
-                <el-tab-pane label="商品投放"></el-tab-pane>
-                <el-tab-pane label="搜索词"></el-tab-pane>
-                <el-tab-pane label="广告位"></el-tab-pane>
             </el-tabs>
         </div>
     </div>
@@ -21,7 +28,10 @@
 <script lang="ts" setup>
 import DateRangePicker from '/@/components/DateRangePicker/index.vue'
 import campaigns from './campaigns/index.vue'
-import {ref} from 'vue'
+import keywords from './keywords/index.vue'
+import SearchTerm from './searchTerms/index.vue'
+import {provide, reactive, ref} from 'vue'
+import {nextTick} from 'process'
 
 const portfolios = ref([])
 const dateRange = ref([])
@@ -30,6 +40,36 @@ function changeDateRange(val: string[]) {
     console.log(val)
 }
 
+const campaignsRef = ref()
+const keywordsRef = ref()
+
+function changeTab() {
+    nextTick(() => {
+        campaignsRef.value.resizeTabChart()
+        keywordsRef.value.resizeTabChart()
+    })
+}
+
+// 提供柱线图数据
+
+let barLineData = reactive({
+    xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+    barData: [12, 13.4, 12.5, 16, 14.5, 15.6, 12.3],
+    yData1: [18, 13, 10, 8, 9, 10, 14.2],
+    yData2: [14, 15, 12, 16, 15, 13, 14.5]
+})
+provide('barLineData', barLineData)
+
+// 提供饼图和柱状图的数据
+let pieBarChartData = reactive({
+    xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+    barData: [[10, 16, 28, 21, 30], [6, 12, 17, 21, 25]],
+    pieData: [{ value: 1048, name: 'Search Engine' },
+        { value: 484, name: 'Union Ads' },
+        { value: 300, name: 'Video Ads' }]
+})
+provide('pieBarChartData', pieBarChartData)
+
 </script>
 
 <style>

+ 42 - 0
src/views/adManage/sb/keywords/api.ts

@@ -0,0 +1,42 @@
+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/spkeywords/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: InfoReq) {
+    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 },
+    });
+}

+ 114 - 0
src/views/adManage/sb/keywords/crud.tsx

@@ -0,0 +1,114 @@
+import * as api from './api'
+import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject, nextTick, ref } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+
+
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const pageRequest = async (query: UserPageQuery) => {
+		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: 600
+			},
+			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: {
+        keywordText: {
+          title: '关键词'
+        },
+				matchType: {
+					title: '匹配类型',
+					type: 'dict-select',
+					search: {
+						show: true
+					},
+					dict: dict({
+						data: [
+							{ value: 'BROAD', label: '广泛匹配' },
+							{ value: 'PHRASE', label: '词组匹配' },
+							{ value: 'EXACT', label: '精准匹配' },
+						]
+					})
+				},
+				state: {
+					title: '状态'
+				},
+				bid: {
+					title: '出价'
+				},
+				"campaign": {
+					title: '广告活动'
+				},
+				"adGroup": {
+					title: '广告组'
+				}
+			}
+		}
+	}
+}

+ 116 - 0
src/views/adManage/sb/keywords/index.vue

@@ -0,0 +1,116 @@
+<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="barLine">
+                        <MetricsCards v-model="metrics" :metric-items="options" @change="changeMetric"></MetricsCards>
+                        <div style="display: flex; justify-content: flex-end">
+                            <el-button-group class="ml-4" style="margin-top: 10px; margin-right: 30px">
+                                <el-button type="primary" @click="changeChartData">日</el-button>
+                                <el-button type="primary" @click="changeChartData">周</el-button>
+                                <el-button type="primary" @click="changeChartData">月</el-button>
+                            </el-button-group>
+                        </div>
+                        <BarLineChart ref="barLineRef" :barLineData="barLineData"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="广告结构" name="bar" :lazy="true">
+                        <BarChart ref="BarChartRef"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点视图" name="scatterView" :lazy="true"></el-tab-pane>
+                </el-tabs>
+            </template>
+        </fs-crud>
+    </fs-page>
+</template>
+
+<script setup>
+import {ref, onMounted, onBeforeUnmount, watch, nextTick, onActivated, reactive} 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 AdStructChart from '/@/views/adManage/sp/campaigns/chartComponents/adStruct.vue'
+import DataTendencyChart from '/@/views/adManage/sp/campaigns/chartComponents/dataTendency.vue'
+import BarLineChart from '/@/components/echartsComponents/BarLineChart.vue'
+import PieBarChart from '/@/components/echartsComponents/PieBarChart.vue'
+import BarChart from "/@/components/echartsComponents/BarChart.vue"
+
+const tabActiveName = ref('barLine')
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: {}})
+
+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 route = useRoute()
+const router = useRouter()
+const BarChartRef = ref()
+const barLineRef = ref()
+
+const barLineData = reactive({
+    xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+    barData: [12, 13.4, 12.5, 16, 14.5, 15.6, 12.3],
+    yData1: [18, 13, 10, 8, 9, 10, 14.2],
+    yData2: [14, 15, 12, 16, 15, 13, 14.5]
+})
+
+onMounted(() => {
+    crudExpose.doRefresh()
+})
+
+const resizeTabChart = () => {
+    if (tabActiveName.value === 'barLine') {
+        barLineRef.value.resizeChart()
+    } else if (tabActiveName.value === 'bar') {
+        BarChartRef.value.resizeChart()
+    }
+}
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
+}
+const changeMetric = () => {
+    console.log(metrics.value)
+}
+
+// todo 发送请求获取数据切换图表
+function changeChartData() {
+    updateData({
+        xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+        barData: [10, 13.4, 12, 14, 20, 14, 11.1],
+        yData1: [7, 2, 4, 8, 4, 15, 10],
+        yData2: [15, 10, 12, 14, 12, 10, 12.5]
+    });
+}
+
+function updateData(newData) {
+    // 更新响应式数据
+    Object.assign(barLineData, newData)
+    // 然后更新图表
+    barLineRef.value.updateChart()
+}
+
+defineExpose({resizeTabChart})
+
+</script>
+
+<style lang="scss">
+.chart-tabs {
+    margin: 5px 0;
+
+    .el-tabs__nav {
+        padding-left: 0 !important;
+    }
+}
+
+</style>

+ 42 - 0
src/views/adManage/sb/searchTerms/api.ts

@@ -0,0 +1,42 @@
+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/spkeywords/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: InfoReq) {
+    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 },
+    });
+}

+ 114 - 0
src/views/adManage/sb/searchTerms/crud.tsx

@@ -0,0 +1,114 @@
+import * as api from './api'
+import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject, nextTick, ref } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+
+
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const pageRequest = async (query: UserPageQuery) => {
+		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: 600
+			},
+			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: {
+        keywordText: {
+          title: '关键词'
+        },
+				matchType: {
+					title: '匹配类型',
+					type: 'dict-select',
+					search: {
+						show: true
+					},
+					dict: dict({
+						data: [
+							{ value: 'BROAD', label: '广泛匹配' },
+							{ value: 'PHRASE', label: '词组匹配' },
+							{ value: 'EXACT', label: '精准匹配' },
+						]
+					})
+				},
+				state: {
+					title: '状态'
+				},
+				bid: {
+					title: '出价'
+				},
+				"campaign": {
+					title: '广告活动'
+				},
+				"adGroup": {
+					title: '广告组'
+				}
+			}
+		}
+	}
+}

+ 112 - 0
src/views/adManage/sb/searchTerms/index.vue

@@ -0,0 +1,112 @@
+<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="barLine">
+                        <MetricsCards v-model="metrics" :metric-items="options" @change="changeMetric"></MetricsCards>
+                        <div style="display: flex; justify-content: flex-end">
+                            <el-button-group class="ml-4" style="margin-top: 10px; margin-right: 30px">
+                                <el-button type="primary" @click="changeChartData">日</el-button>
+                                <el-button type="primary" @click="changeChartData">周</el-button>
+                                <el-button type="primary" @click="changeChartData">月</el-button>
+                            </el-button-group>
+                        </div>
+                        <BarLineChart ref="barLineRef" :barLineData="barLineData"/>
+                    </el-tab-pane>
+                    <el-tab-pane label="散点视图" name="scatterView" :lazy="true"></el-tab-pane>
+                </el-tabs>
+            </template>
+        </fs-crud>
+    </fs-page>
+</template>
+
+<script setup>
+import {ref, onMounted, onBeforeUnmount, watch, nextTick, onActivated, reactive} 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 AdStructChart from '/@/views/adManage/sp/campaigns/chartComponents/adStruct.vue'
+import DataTendencyChart from '/@/views/adManage/sp/campaigns/chartComponents/dataTendency.vue'
+import BarLineChart from '/@/components/echartsComponents/BarLineChart.vue'
+import PieBarChart from '/@/components/echartsComponents/PieBarChart.vue'
+import BarChart from "/@/components/echartsComponents/BarChart.vue"
+
+const tabActiveName = ref('barLine')
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: {}})
+
+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 route = useRoute()
+const router = useRouter()
+const barLineRef = ref()
+
+const barLineData = reactive({
+    xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+    barData: [12, 13.4, 12.5, 16, 14.5, 15.6, 12.3],
+    yData1: [18, 13, 10, 8, 9, 10, 14.2],
+    yData2: [14, 15, 12, 16, 15, 13, 14.5]
+})
+
+onMounted(() => {
+    crudExpose.doRefresh()
+})
+
+const resizeTabChart = () => {
+    if (tabActiveName.value === 'barLine') {
+        barLineRef.value.resizeChart()
+    } else if (tabActiveName.value === 'bar') {
+        // BarChartRef.value.resizeChart()
+    }
+}
+const changeTab = () => {
+    nextTick(() => {
+        resizeTabChart()
+    })
+}
+const changeMetric = () => {
+    console.log(metrics.value)
+}
+
+// todo 发送请求获取数据切换图表
+function changeChartData() {
+    updateData({
+        xData: ['2023-10-18', '2023-10-19', '2023-10-20', '2023-10-21', '2023-10-22', '2023-10-23', '2023-10-24'],
+        barData: [10, 13.4, 12, 14, 20, 14, 11.1],
+        yData1: [7, 2, 4, 8, 4, 15, 10],
+        yData2: [15, 10, 12, 14, 12, 10, 12.5]
+    });
+}
+
+function updateData(newData) {
+    // 更新响应式数据
+    Object.assign(barLineData, newData)
+    // 然后更新图表
+    barLineRef.value.updateChart()
+}
+
+defineExpose({resizeTabChart})
+
+</script>
+
+<style lang="scss">
+.chart-tabs {
+    margin: 5px 0;
+
+    .el-tabs__nav {
+        padding-left: 0 !important;
+    }
+}
+
+</style>

+ 8 - 0
src/views/adManage/sp/campaigns/api.ts

@@ -56,3 +56,11 @@ export function getLineData(query: UserPageQuery) {
         params: query
     })
 }
+
+export function getAdStructureData(query) {
+    return request({
+        url: apiPrefix + "structure/",
+        method: 'GET',
+        params: query
+    })
+}

+ 2 - 2
src/views/adManage/sp/campaigns/campaignDetail/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="asj-container">
     <div class="asj-detail-header">
-      <span style="font-size: x-large; font-weight: bold; color: #0f1111;margin: 5px;"> 
+      <span style="font-size: x-large; font-weight: bold; color: #0f1111;margin: 5px;">
         <span> {{ campaignInfo.campaignName }}</span>
       </span>
       <div class="asj-detail-info">
@@ -55,4 +55,4 @@ onMounted(async () => {
 
 <style lang="scss">
 
-</style>
+</style>

+ 501 - 41
src/views/adManage/sp/campaigns/chartComponents/adStruct.vue

@@ -1,52 +1,512 @@
 <template>
-  <div style="height: 500px;" ref="lineRef"></div>
+    <div v-loading="loading">
+        <el-row :gutter="5">
+            <el-col :span="8">
+                <div>
+                    <TextSelector v-model="modelValue" :options="pieOptions" @change="changePie" style="margin-top: 5px"/>
+                </div>
+                <div ref="pie" style="height: 500px;"></div>
+            </el-col>
+            <el-col :span="16">
+                <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;"/>
+                    <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;"/>
+                </div>
+                <div ref="bar" style="height: 500px;"></div>
+            </el-col>
+        </el-row>
+    </div>
 </template>
 
-<script lang="ts" setup>
-import { ref,onMounted, onBeforeUnmount } from 'vue'
-import * as echarts from 'echarts'
+<script setup>
+import { onMounted, ref, inject } from "vue"
+import * as echarts from "echarts"
+import TextSelector from '/@/components/TextSelector/index.vue'
+import { getAdStructureData } from "/@/views/adManage/sp/campaigns/api"
 
-defineOptions({
-  name: "AdStructChart"
-})
+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)
+
+// 下拉框相关
+const pieOptions = [
+    {
+        value: 'Spend',
+        label: '花费',
+    },
+    {
+        value: 'TotalSales',
+        label: '销售额',
+    },
+    {
+        value: 'TotalPurchases',
+        label: '订单数',
+    },
+    {
+        value: 'TotalUnitOrdered',
+        label: '销量',
+    },
+    {
+        value: 'Impression',
+        label: '曝光量',
+    },
+    {
+        value: 'Click',
+        label: '点击量',
+    },
+]
+const metricMap = {
+    'Spend': '花费',
+    'TotalSales': '销售额',
+    'TotalPurchases': '订单数',
+    'TotalUnitOrdered': '销量',
+    'Impression': '曝光量',
+    'Click': '点击量',
+}
+let modelValue = ref(pieOptions[0].value)
+
+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)
+    pieChart = echarts.init(pie.value)
 
-let chartObj:any
-const lineRef = ref()
-const resizeChart = () => { chartObj.resize() }
-const addResize = () => { window.addEventListener('resize', resizeChart) }
-const removeResize = () => { window.removeEventListener('resize', resizeChart) }
-
-onMounted(() => {
-	initLine()
-	addResize()
-});
-onBeforeUnmount(() => {
-	if(chartObj) {
-		chartObj.dispose()
-    chartObj = null
-	}
-	removeResize()
+    window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
+    setTimeout(() => {
+        resizeChart()
+    }, 0)
+
+    await initPieBarData()
+    initChart()
 })
-const initLine = () => {
-	chartObj = echarts.init(lineRef.value)
-	const option = {
-		xAxis: {
-			data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-		},
-		yAxis: {},
-		series: [
-			{
-				type: 'bar',
-				data: [23, 24, 18, 25, 27, 28, 25]
-			}
-		]
-	}
-	chartObj.setOption(option)
-}
-defineExpose({resizeChart})
+
+// 获取总数据
+let allData = null
+
+async function setAdStructureData() {
+    allData = await getAdStructureData({ start: dateRange.value[0], end: dateRange.value[1], profile: '3006125408623189' })
+    console.log('allData.data', allData.data)
+    return allData.data
+}
+
+// 饼图总数据和柱状图总数据
+let pieData = null
+let barData = null
+let pieBarData = null
+// 柱状图初始数据
+let ACOSList
+let SpendList
+let ClassificationList
+let classificationMapList
+
+async function initPieBarData() {
+    pieBarData = await setAdStructureData()
+    pieData = [
+        { value: pieBarData.pie_data[0].Spend, name: '自动' },
+        { value: pieBarData.pie_data[1].Spend, name: '手动' },
+    ]
+    barData = pieBarData.line_data
+    // 柱状图初始化数据
+    ACOSList = barData.map(item => item.ACOS)
+    SpendList = barData.map(item => item.Spend)
+    ClassificationList = barData.map(item => item.Classification)
+    const classificationMap = {
+        'BROAD': '关键词-广泛',
+        'category': '品类',
+        'EXACT': '关键词-精准',
+        'asin': '商品',
+        'PHRASE': '关键词-词组',
+        'close-match': '紧密匹配',
+        'loose-match': '广泛匹配',
+        'substitutes': '同类商品',
+        'complements': '关联商品'
+    }
+    classificationMapList = ClassificationList.map(item => classificationMap[item])
+    console.log(ClassificationList)
+    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
+    option2.series[0].data = [
+        { 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)
+        }
+        // barChart.clear()
+        option.series[0].data = barValues
+        barChart.setOption(option)
+    } catch (error) {
+        console.log(error)
+    }
+}
+
+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)
+    }
+}
+
+// 初始化图表
+function initChart() {
+    // 柱状图配置
+    option = {
+        tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+                type: 'cross',
+                label: {
+                    backgroundColor: '#6a7985'
+                }
+            }
+        },
+        // legend: {data: ['数据1', '数据2'],},
+        toolbox: {
+            feature: {
+                saveAsImage: { yAxisIndex: 'none' }
+            }
+        },
+        grid: {
+            top: 55, right: 60, bottom: 55, left: 55,
+
+        },
+        xAxis: [
+            {
+                type: 'category',
+                boundaryGap: true,
+                data: classificationMapList,
+                axisLabel: {
+                    rotate: 30, // 将标签旋转45度
+                    fontSize: 12
+                },
+            },
+        ],
+        yAxis: [
+            {
+                type: 'value',
+                // name: '数据1',
+                // axisLabel: {
+                //     formatter: '{value} %'
+                // },
+                axisLine: {
+                    show: true,
+                    lineStyle: {
+                        color: '#3a83f7' // 第一个 Y 轴的颜色
+                    }
+                }
+            },
+            {
+                type: 'value',
+                // name: '数据2',
+                splitLine: {
+                    show: false
+                },
+                // axisLabel: {
+                //     formatter: '{value} 单位2'
+                // },
+                axisLine: {
+                    show: true,
+                    lineStyle: {
+                        color: '#f19a37' // 第一个 Y 轴的颜色
+                    }
+                }
+            }
+        ],
+        series: [
+            {
+                // name: '数据1',
+                type: 'bar',
+                barWidth: '15%',
+                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: [15, 15, 0, 0],
+                },
+            },
+            {
+                // name: '数据2',
+                type: 'bar',
+                barWidth: '15%',
+                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: [15, 15, 0, 0],
+                },
+            },
+        ],
+    }
+    barChart.setOption(option)
+    // 饼图配置
+    option2 = {
+        tooltip: {
+            trigger: 'item',
+        },
+        series: [
+            {
+                // name: modelValue.value,
+                type: 'pie',
+                radius: ['20%', '40%'],
+                avoidLabelOverlap: false,
+                itemStyle: {
+                    // borderRadius: 10,
+                    borderWidth: 1, // 设置边框的宽度
+                    borderColor: '#fff', // 将边框颜色设置为白色或图表背景颜色
+                },
+                emphasis: {
+                    label: {
+                        show: true,
+                        // fontSize: 40,
+                        fontWeight: 'bold',
+                    }
+                },
+                label: {
+                    show: true,
+                    position: 'outside', // 标签显示在外侧
+                    // formatter: `{b}\n{b|${metricMap[modelValue.value]}:{c}}\n{d}%`, // 标签文本格式器
+                    formatter: (params) => {
+                        return params.name + '\n' + '{b|' + metricMap[modelValue.value] + ':}' + '{b|' + params.data.value + '}' + '\n' + params.percent + '%'
+                    },
+                    rich: {
+                        b: {
+                            color: '#4C5058',
+                            fontSize: 15,
+                            fontWeight: 'bold',
+                            lineHeight: 33
+                        },
+                    }
+                },
+                labelLine: {
+                    normal: {
+                        show: true
+                    }
+                },
+                data: pieData
+            }
+        ]
+    }
+    pieChart.setOption(option2)
+    resizeChart()
+}
+
+function resizeChart() {
+    barChart.resize()
+    pieChart.resize()
+}
+
+defineExpose({ resizeChart })
 
 </script>
 
 <style scoped>
 
-</style>
+</style>

+ 3 - 2
src/views/adManage/sp/campaigns/index.vue

@@ -33,7 +33,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, Ref, nextTick, onBeforeMount } from 'vue';
+import {ref, onMounted, Ref, nextTick, onBeforeMount, inject} from 'vue'
 import { useFs, FsPage } from '@fast-crud/fast-crud';
 import { createCrudOptions } from './crud';
 import { useShopInfo } from '/@/stores/shopInfo'
@@ -69,8 +69,9 @@ const jumpGroup = (row: any) => {
 	})
 }
 
+
 defineExpose({ resizeTabChart })
 </script>
 
 <style lang="scss">
-</style>
+</style>

+ 38 - 39
src/views/adManage/sp/index.vue

@@ -1,37 +1,36 @@
 <template>
-  <div class="asj-container">
-    <div class="public-search">
-      <DateRangePicker v-model="dateRange" :timezone="shopInfo.profile.time_zone"></DateRangePicker>
-      <el-select v-model="selectedPortfolios" placeholder="广告组合" clearable multiple>
-        <el-option v-for="info of portfolios" :label="info.name" :value="info.portfolioId"></el-option>
-      </el-select>
+    <div class="asj-container">
+        <div class="public-search">
+            <DateRangePicker v-model="dateRange" :timezone="shopInfo.profile.time_zone"></DateRangePicker>
+            <el-select v-model="selectedPortfolios" placeholder="广告组合" clearable multiple>
+                <el-option v-for="info of portfolios" :label="info.name" :value="info.portfolioId"></el-option>
+            </el-select>
+        </div>
+        <el-tabs v-model="tabActiveName" class="asj-tabs" @tab-change="changeTab">
+            <el-tab-pane label="广告活动" name="campaigns">
+                <Campaigns ref="campaignsRef"/>
+            </el-tab-pane>
+            <el-tab-pane label="关键词投放" name="keywords" :lazy="true">
+                <Keywords ref="keywordsRef"/>
+            </el-tab-pane>
+            <el-tab-pane label="商品投放" :lazy="true"></el-tab-pane>
+            <el-tab-pane label="搜索词" :lazy="true"></el-tab-pane>
+            <el-tab-pane label="广告位" :lazy="true"></el-tab-pane>
+        </el-tabs>
     </div>
-    <el-tabs v-model="tabActiveName" class="asj-tabs" @tab-change="changeTab">
-      <el-tab-pane label="广告活动" name="campaigns">
-        <Campaigns ref="campaignsRef"/>
-      </el-tab-pane>
-      <el-tab-pane label="关键词投放" name="keywords" :lazy="true">
-        <Keywords ref="keywordsRef"/>
-      </el-tab-pane>
-      <el-tab-pane label="商品投放" :lazy="true">
-        
-      </el-tab-pane>
-      <el-tab-pane label="搜索词" :lazy="true"></el-tab-pane>
-      <el-tab-pane label="广告位" :lazy="true"></el-tab-pane>
-    </el-tabs>
-  </div>
 </template>
 
 <script lang="ts" setup>
 import DateRangePicker from '/@/components/DateRangePicker/index.vue'
 import Campaigns from './campaigns/index.vue'
 import Keywords from './keywords/index.vue'
-import { recentDaysRange } from '/@/views/adManage/utils/tools'
-import { ref, onBeforeMount, Ref, provide, watch } from 'vue'
-import { useShopInfo } from '/@/stores/shopInfo'
-import { usePublicData } from '/@/stores/publicData'
-import { GetList } from '/@/views/adManage/portfolios/api'
-import { nextTick } from 'process'
+import {recentDaysRange} from '/@/views/adManage/utils/tools'
+import {ref, onBeforeMount, Ref, provide, watch, reactive} from 'vue'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import {GetList} from '/@/views/adManage/portfolios/api'
+import {nextTick} from 'process'
+import {getAdStructureData} from '/@/views/adManage/sp/campaigns/api'
 
 
 const shopInfo = useShopInfo()
@@ -39,7 +38,7 @@ const publicData = usePublicData()
 const selectedPortfolios: Ref<string[]> = ref([])
 const portfolios: Ref<Portfolio[]> = ref([])
 const dateRange: Ref<string[]> = ref(['2023-11-01', '2023-11-04'])
-const tabActiveName = ref("campaigns")
+const tabActiveName = ref('campaigns')
 const keywordsRef = ref()
 const campaignsRef = ref()
 
@@ -47,23 +46,23 @@ publicData.updateData({dateRange: dateRange.value})
 provide('dateRange', dateRange)
 
 onBeforeMount(async () => {
-  console.log(shopInfo.profile)
-  // dateRange.value = recentDaysRange(shopInfo.profile.time_zone, 7)
+    console.log(shopInfo.profile)
+    // dateRange.value = recentDaysRange(shopInfo.profile.time_zone, 7)
 
-  const resp:APIResponseData = await GetList({ limit: 999 })
-  portfolios.value = resp.data
+    const resp: APIResponseData = await GetList({limit: 999})
+    portfolios.value = resp.data
 })
 const changeTab = () => {
-  nextTick(() => {
-    campaignsRef.value.resizeTabChart()
-    keywordsRef.value.resizeTabChart()
-  })
+    nextTick(() => {
+        campaignsRef.value.resizeTabChart()
+        keywordsRef.value.resizeTabChart()
+    })
 }
 watch(
-  dateRange, 
-  () => {
-    publicData.updateData({dateRange: dateRange.value})
-  }
+        dateRange,
+        () => {
+            publicData.updateData({dateRange: dateRange.value})
+        }
 )
 
 </script>

+ 108 - 108
src/views/adManage/sp/keywords/crud.tsx

@@ -1,114 +1,114 @@
 import * as api from './api'
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
-import { inject, nextTick, ref } from 'vue'
-import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import {dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet} from '@fast-crud/fast-crud'
+import {inject, nextTick, ref} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
 
 
-export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const pageRequest = async (query: UserPageQuery) => {
-		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);
-	};
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+    const pageRequest = async (query: UserPageQuery) => {
+        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');
+    //权限判定
+    const hasPermissions = inject('$hasPermissions')
 
-	return {
-		crudOptions: {
-			table: {
-				height: 600
-			},
-			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: {
-        keywordText: {
-          title: '关键词'
-        },
-				matchType: {
-					title: '匹配类型',
-					type: 'dict-select',
-					search: {
-						show: true
-					},
-					dict: dict({
-						data: [
-							{ value: 'BROAD', label: '广泛匹配' },
-							{ value: 'PHRASE', label: '词组匹配' },
-							{ value: 'EXACT', label: '精准匹配' },
-						]
-					})
-				},
-				state: {
-					title: '状态'
-				},
-				bid: {
-					title: '出价'
-				},
-				"campaign": {
-					title: '广告活动'
-				},
-				"adGroup": {
-					title: '广告组'
-				}
-			}
-		}
-	}
+    return {
+        crudOptions: {
+            table: {
+                height: 600
+            },
+            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: {
+                keywordText: {
+                    title: '关键词'
+                },
+                matchType: {
+                    title: '匹配类型',
+                    type: 'dict-select',
+                    search: {
+                        show: true
+                    },
+                    dict: dict({
+                        data: [
+                            {value: 'BROAD', label: '广泛匹配'},
+                            {value: 'PHRASE', label: '词组匹配'},
+                            {value: 'EXACT', label: '精准匹配'},
+                        ]
+                    })
+                },
+                state: {
+                    title: '状态'
+                },
+                bid: {
+                    title: '出价'
+                },
+                'campaign': {
+                    title: '广告活动'
+                },
+                'adGroup': {
+                    title: '广告组'
+                }
+            }
+        }
+    }
 }

+ 42 - 0
src/views/authorization/api.ts

@@ -0,0 +1,42 @@
+import { request } from '/@/utils/service';
+import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+import XEUtils from 'xe-utils';
+
+export const apiPrefix = '/api/ad_manage/';
+export function GetList(query: PageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: InfoReq) {
+    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 },
+    });
+}

+ 150 - 0
src/views/authorization/crud.tsx

@@ -0,0 +1,150 @@
+import * as api from './api'
+import {dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet} from '@fast-crud/fast-crud'
+import {inject, nextTick, ref} from 'vue'
+import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {useRouter} from 'vue-router'
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const pageRequest = async (query: UserPageQuery) => {
+		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')
+
+	// todo 点击新建广告活动进行路由跳转(还有问题)
+	const router = useRouter()
+
+	function goCreate() {
+		router.push('/createcampaigns')
+	}
+
+	return {
+		crudOptions: {
+			table: {
+				height: 800
+			},
+			container: {
+				fixedHeight: false
+			},
+			actionbar: {
+				show: true,
+				buttons: {
+					add: {
+						text: 'xxx',
+						show: false
+					},
+					create: {
+						text: '新建广告活动',
+						type: 'primary',
+						show: true,
+						click() {
+							goCreate()
+						}
+					}
+				}
+			},
+			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: {
+				campaignName: {
+					title: '广告活动',
+					column: {
+						width: '150px'
+					},
+					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: '状态'
+				},
+				// state: {
+				//     title: '竞价'
+				// },
+				startDate: {
+					title: '开始日期'
+				},
+				endDate: {
+					title: '结束日期'
+				},
+				portfolio: {
+					title: '广告组合'
+				},
+				budget: {
+					title: '预算'
+				},
+				...BaseColumn
+			}
+		}
+	}
+}

+ 30 - 0
src/views/authorization/index.vue

@@ -0,0 +1,30 @@
+<template>
+    <fs-page class="fs-page-custom">
+        <fs-crud ref="crudRef" v-bind="crudBinding">
+            <template #header-middle>
+
+            </template>
+        </fs-crud>
+    </fs-page>
+</template>
+
+<script setup>
+import { ref, onMounted, inject, nextTick } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+import BarLineChart from '/@/components/echartsComponents/BarLineChart.vue'
+import PieBarChart from '/@/components/echartsComponents/PieBarChart.vue'
+import ScatterChart from '/@/components/echartsComponents/ScatterChart.vue'
+import MetricsCards from "/@/components/MetricsCards/index.vue"
+
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} })
+
+onMounted(() => {
+    crudExpose.doRefresh()
+})
+
+</script>
+
+<style scoped>
+
+</style>