Jelajahi Sumber

新增: 展示型推广SD-商品投放页面

WanGxC 1 tahun lalu
induk
melakukan
ca46f943a3

+ 3 - 0
.prettierrc.js

@@ -1,4 +1,6 @@
 module.exports = {
+	singleAttributePerLine: false,
+	htmlWhitespaceSensitivity: 'ignore',
 	// 一行最多多少个字符
 	printWidth: 150,
 	// 指定每个缩进级别的空格数
@@ -19,6 +21,7 @@ module.exports = {
 	bracketSpacing: true,
 	// jsx 标签的反尖括号需要换行
 	jsxBracketSameLine: false,
+	bracketSameLine: false,
 	// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
 	arrowParens: 'always',
 	// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码

+ 0 - 4
src/views/adManage/sd/campaigns/api.ts

@@ -49,7 +49,6 @@ export function getCardData(query: UserPageQuery) {
 }
 
 export function getLineHourData(query: UserPageQuery) {
-    query["dateRangeType"] = "D"
     return request({
         url: "/api/ad_manage/sdcampaigns/ams/hourly_trend",
         method: 'GET',
@@ -58,7 +57,6 @@ export function getLineHourData(query: UserPageQuery) {
 }
 
 export function getLineData(query: UserPageQuery) {
-    query["dateRangeType"] = "D"
     return request({
         url: apiPrefix + "trend/daily",
         method: 'GET',
@@ -67,7 +65,6 @@ export function getLineData(query: UserPageQuery) {
 }
 
 export function getLineWeekData(query: UserPageQuery) {
-    query["dateRangeType"] = "W"
     return request({
         url: apiPrefix + "trend/weekly",
         method: 'GET',
@@ -76,7 +73,6 @@ export function getLineWeekData(query: UserPageQuery) {
 }
 
 export function getLineMonthData(query: UserPageQuery) {
-    query["dateRangeType"] = "M"
     return request({
         url: apiPrefix + "trend/monthly",
         method: 'GET',

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

@@ -10,7 +10,8 @@
                 :fetchCard="getCardData"
                 :fetchLine="getLineData"
                 :fetch-line-month="getLineMonthData"
-                :fetch-line-week="getLineWeekData">
+                :fetch-line-week="getLineWeekData"
+                :fetch-line-hour="getLineHourData">
             </DataTendencyChart>
           </el-tab-pane>
           <el-tab-pane label="广告结构" name="adStruct">
@@ -66,7 +67,7 @@ import {storeToRefs} from 'pinia'
 import {useRouter} from 'vue-router'
 import AdStructChart from './chartComponents/adStruct.vue'
 import DataTendencyChart from '/@/views/adManage/sd/chartComponents/dataTendency.vue'
-import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData, getLineHourData} from './api'
 import {SdBaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
 import DataCompare from '/@/components/dataCompare/index.vue'
 

+ 10 - 0
src/views/adManage/sd/chartComponents/dataTendency.vue

@@ -2,6 +2,7 @@
   <div v-loading="loading">
     <MetricsCards v-model="metrics" :metric-items="metricsItems" @change="changeMetric"></MetricsCards>
     <el-radio-group v-model="statDim" class="chart-button-group" @change="changeStatDim">
+      <el-radio-button label="hour">时</el-radio-button>
       <el-radio-button label="day">日</el-radio-button>
       <el-radio-button label="week" :disabled="!props.fetchLineWeek">周</el-radio-button>
       <el-radio-button label="month" :disabled="!props.fetchLineWeek">月</el-radio-button>
@@ -31,6 +32,7 @@ interface Props {
   fetchLine: Function,
   fetchLineMonth?: Function,
   fetchLineWeek?: Function,
+  fetchLineHour?: Function,
   query: {[key: string]: any},
   initMetric?: ShowMetric[],
   metricEnum?: {[key: string]: string}[]
@@ -78,6 +80,7 @@ const option: any = {
   yAxis: [
     {
       id: 0,
+      min: 0,
       type: 'value',
       name: '',
       splitLine: {
@@ -91,6 +94,7 @@ const option: any = {
     },
     {
       id: 1,
+      min: 0,
       type: 'value',
       name: '',
       position: 'right',
@@ -107,6 +111,7 @@ const option: any = {
     },
     {
       id: 2,
+      min: 0,
       type: 'value',
       position: 'right',
       offset: 90,
@@ -249,6 +254,11 @@ const getDataset = async () => {
       const resp = await props.fetchLineMonth(queryParams.value)
       return resp.data
     }
+  } else if (statDim.value === 'hour') {
+    if (props.fetchLineHour) {
+      const resp = await props.fetchLineHour(queryParams.value)
+      return resp.data
+    }
   } else {
     const resp = await props.fetchLine(queryParams.value)
     return resp.data

+ 3 - 3
src/views/adManage/sd/index.vue

@@ -33,7 +33,7 @@ import { GetAllPortfolios } from '/@/views/adManage/portfolios/api'
 import DateRangePicker from '/@/components/DateRangePicker/index.vue'
 import Campaigns from '/@/views/adManage/sd/campaigns/index.vue'
 import Keywords from '/@/views/adManage/sb/keywords/index.vue'
-import Targets from '/@/views/adManage/sb/targets/index.vue'
+import Targets from '/@/views/adManage/sd/targets/index.vue'
 import SearchTerm from '/@/views/adManage/sb/searchTerm/index.vue'
 import AdvertisedProducts from './advertisedProducts/index.vue'
 import PurchasedOtherProducts from './purchasedOtherProducts/index.vue'
@@ -48,7 +48,7 @@ const tabActiveName = ref('Campaigns')
 const tabsComponents: any = {
   Campaigns,
   // Keywords,
-  // Targets,
+  Targets,
   // AdvertisedProducts,
   // PurchasedOtherProducts,
   // SearchTerm,
@@ -57,7 +57,7 @@ const tabsComponents: any = {
 const tabs = [
   { label: '广告活动', name: 'Campaigns' },
   // { label: '关键词', name: 'Keywords' },
-  // { label: '商品投放', name: 'Targets' },
+  { label: '商品投放', name: 'Targets' },
   // { label: '推广商品', name: 'AdvertisedProducts' },
   // { label: '购买的其他商品', name: 'PurchasedOtherProducts' },
   // { label: '搜索词', name: 'SearchTerm' },

+ 87 - 0
src/views/adManage/sd/targets/api.ts

@@ -0,0 +1,87 @@
+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/sdtarget/report/';
+export function GetList(query: UserPageQuery) {
+    query["tactic"] = "T00020"
+    return request({
+        url: '/api/ad_manage/sdtarget/',
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: any) {
+    return request({
+        url: apiPrefix + id + "/",
+        method: 'get',
+    });
+}
+
+export function AddObj(obj: AddReq) {
+    return request({
+        url: apiPrefix,
+        method: 'post',
+        data: obj,
+    });
+}
+
+export function UpdateObj(obj: EditReq) {
+    return request({
+        url: apiPrefix + obj.id + '/',
+        method: 'put',
+        data: obj,
+    });
+}
+
+export function DelObj(id: DelReq) {
+    return request({
+        url: apiPrefix + id + '/',
+        method: 'delete',
+        data: { id },
+    });
+}
+
+export function getCardData(query: UserPageQuery) {
+    query["tactic"] = "T00020"
+    return request({
+        url: apiPrefix + "amount",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineData(query: UserPageQuery) {
+    query["tactic"] = "T00020"
+    return request({
+        url: apiPrefix + "trend/daily",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineWeekData(query: UserPageQuery) {
+    query["tactic"] = "T00020"
+    return request({
+        url: apiPrefix + "trend/weekly",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineMonthData(query: UserPageQuery) {
+    query["tactic"] = "T00020"
+    return request({
+        url: apiPrefix + "trend/monthly",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getAdStructureData(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + "structure/",
+        method: 'GET',
+        params: query
+    })
+}

+ 236 - 0
src/views/adManage/sd/targets/chartComponents/adStruct.vue

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

+ 261 - 0
src/views/adManage/sd/targets/chartComponents/dataTendency.vue

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

+ 178 - 0
src/views/adManage/sd/targets/crud.tsx

@@ -0,0 +1,178 @@
+import * as api from './api'
+import {AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery} from '@fast-crud/fast-crud'
+import {inject} from 'vue'
+import {SdBaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
+
+export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+  const pageRequest = async (query: UserPageQuery) => {
+    const params = parseQueryParams(context.value)
+    XEUtils.assign(query, params)
+    return await api.GetList(query)
+  }
+  const editRequest = async ({form, row}: EditReq) => {
+    form.id = row.id
+    return await api.UpdateObj(form)
+  }
+  const delRequest = async ({row}: DelReq) => {
+    return await api.DelObj(row.id)
+  }
+  const addRequest = async ({form}: AddReq) => {
+    return await api.AddObj(form)
+  }
+
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {
+        height: 800,
+        headerCellStyle: {
+          backgroundColor: '#f6f7fa', // 直接设置背景颜色
+          height: '20px',
+          borderRight: 'none',
+        },
+        cellStyle: {
+          border: 'none',
+          borderBottom: '0.5px solid #ddd',
+        },
+        showSummary: true,
+        stripe: false
+      },
+      container: {
+        fixedHeight: false
+      },
+      actionbar: {
+        show: false,
+        buttons: {
+          add: {
+            show: false
+          },
+        }
+      },
+      search: {
+        show: false
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true
+          },
+          compact: {
+            show: false
+          }
+        }
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 100,
+        align: 'center',
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            show: false
+            // iconRight: 'Delete',
+            // type: 'text',
+            // text: null
+            // show: hasPermissions('dictionary:Delete'),
+          },
+        },
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          column: {
+            show: false
+          },
+          form: {
+            show: false
+          }
+        },
+        resolvedExpression_value:{
+          title: '商品和分类',
+          column: {
+            fixed: 'left',
+            width: 180,
+            sortable: true
+          },
+        },
+        state: {
+          title: '状态',
+          column: {
+            width: '90px',
+            align: 'center',
+            sortable: true,
+          },
+          type: 'dict-select',
+          search: {
+            show: true
+          },
+          dict: dict({
+            data: [
+              {value: 'paused', label: '已暂停', color: 'warning'},
+              {value: 'enabled', label: '投放中', color: 'success'},
+            ]
+          })
+        },
+        campaignName: {
+          title: '广告活动名称',
+          column: {
+            width: 180,
+          }
+        },
+        adGroupName: {
+          title: '广告组名称',
+          column: {
+            width: 180,
+          }
+        },
+        suggestedBid: {
+          title: '建议竞价',
+          column: {
+            width: 130,
+            align: 'right'
+          }
+        },
+        bid: {
+          title: '出价',
+          column: {
+            width: 80,
+            align: 'center',
+            sortable: true,
+            formatter: (row) => {
+              return '$' + row.value
+            }
+          }
+        },
+        suggestedBid_lower: {
+          column: {
+            show: false,
+          }
+        },
+        suggestedBid_upper: {
+          column: {
+            show: false,
+          }
+        },
+
+        ...SdBaseColumn
+      }
+    }
+  }
+}

+ 221 - 0
src/views/adManage/sd/targets/index.vue

@@ -0,0 +1,221 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #header-middle>
+        <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card" @tab-change="changeTab">
+          <el-tab-pane label="数据趋势" name="dataTendency">
+            <DataTendencyChart
+                :query="queryParams"
+                v-if="tabActiveName === 'dataTendency'"
+                :fetchCard="getCardData"
+                :fetch-line-month="getLineMonthData"
+                :fetch-line-week="getLineWeekData"
+                :fetchLine="getLineData">
+            </DataTendencyChart>
+          </el-tab-pane>
+          <el-tab-pane label="广告结构" name="adStruct">
+            <AdStructChart v-if="tabActiveName === 'adStruct'"/>
+          </el-tab-pane>
+          <el-tab-pane label="散点视图" name="scatterView"></el-tab-pane>
+        </el-tabs>
+      </template>
+      <template #cell_percentTimeInBudget="scope">
+        <el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0"/>
+      </template>
+
+      <template #cell_resolvedExpression_value="scope">
+        <el-tooltip effect="dark" placement="top">
+          <template #content>
+            <p>{{ scope.row.resolvedExpression_value }}</p>
+            {{ scope.row.ASIN }}
+          </template>
+          <div>
+            <el-link :underline="false" class="ellipsis-inline" style="color: rgb(30, 33, 40); display: inline-block; max-width: 100%;">
+              {{ scope.row.resolvedExpression_value }}
+            </el-link>
+            <br>
+            <span class="ellipsis">
+              <span style="color: rgb(109, 119, 132)">ASIN: </span>
+              <span style="color: rgb(30, 33, 40)">{{ scope.row.ASIN }}</span>
+            </span>
+          </div>
+        </el-tooltip>
+      </template>
+      
+      <template #cell_campaignName="scope">
+        <el-tooltip effect="dark" :content="scope.row.campaignName" placement="top">
+          <el-link type="primary" :underline="false" @click="jumpGroup(scope.row)">
+            <div class="en-text">{{ scope.row.campaignName }}</div>
+          </el-link>
+        </el-tooltip>
+      </template>
+      <template #cell_adGroupName="scope">
+        <el-tooltip effect="dark" :content="scope.row.adGroupName" placement="top">
+          <el-link type="primary" :underline="false" @click="jumpGroup(scope.row)">
+            <div class="en-text">{{ scope.row.adGroupName }}</div>
+          </el-link>
+        </el-tooltip>
+      </template>
+      <template #cell_suggestedBid="scope">
+        <div>{{ scope.row.suggestedBid ? `$${scope.row.suggestedBid}` : '--' }}</div>
+        <div class="text-range">
+          {{ scope.row.suggestedBid_lower ? `$${scope.row.suggestedBid_lower}` : '--' }} ~ 
+          {{ scope.row.suggestedBid_upper ? `$${scope.row.suggestedBid_upper}` : '--' }}
+        </div>
+      </template>      
+      <template #cell_MissedImpressions="scope">
+        {{ scope.row.MissedImpressionsLower ?? '0' }} ~ {{ scope.row.MissedImpressionsUpper ?? '0' }}
+      </template>
+      <template #cell_MissedClicks="scope"> {{ scope.row.MissedClicksLower ?? '0' }} ~ {{ scope.row.MissedClicksUpper ?? '0' }}</template>
+      <template #cell_MissedSales="scope"> {{ scope.row.MissedSalesLower ?? '0' }} ~ {{ scope.row.MissedSalesUpper ?? '0' }}</template>
+
+      <template v-for="field of Object.keys(SbBaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+            :field="field"
+            :value="scope.row[field]"
+            :prev-val="scope.row[`prev${field}`]"
+            :gap-val="scope.row[`gap${field}`]"
+            :date-range="dateRange"
+            :show-compare="showCompare"/>
+      </template>
+      <template #toolbar-left>
+        <div class="campare-switch">
+          <span>数据对比 </span>
+          <el-switch v-model="showCompare" size="small"/>
+        </div>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import {nextTick, onMounted, ref, watch} from 'vue'
+import {FsPage, useFs} from '@fast-crud/fast-crud'
+import {createCrudOptions} from './crud'
+import {useRoute, useRouter} from 'vue-router'
+import DataTendencyChart from '/@/views/adManage/sb/chartComponents/dataTendency.vue'
+import {useShopInfo} from '/@/stores/shopInfo'
+import {usePublicData} from '/@/stores/publicData'
+import AdStructChart from './chartComponents/adStruct.vue'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {storeToRefs} from 'pinia'
+import {SbBaseColumn} from '/@/views/adManage/utils/commonTabColumn'
+import DataCompare from '/@/components/dataCompare/index.vue'
+
+
+const tabActiveName = ref('dataTendency')
+const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const {dateRange} = storeToRefs(publicData)
+const {profile} = storeToRefs(shopInfo)
+const queryParams = ref({
+  profileId: profile.value.profile_id,
+  dateRange
+})
+
+const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: queryParams})
+const route = useRoute()
+const router = useRouter()
+const adStructChartRef = ref()
+const dataTendencyRef = ref()
+const showCompare = ref(false)
+
+
+onMounted(() => {
+  crudExpose.doRefresh()
+})
+
+const jumpGroup = (row: any) => {
+  router.push({
+    name: 'CampaignDetail',
+    query: {campaignId: row.campaignId, tagsViewName: row.campaignName},
+  })
+}
+
+const resizeTabChart = () => {
+  if (tabActiveName.value === 'dataTendency') {
+    dataTendencyRef.value.resizeChart()
+  } else if (tabActiveName.value === 'adStruct') {
+    adStructChartRef.value.resizeChart()
+  }
+}
+const changeTab = () => {
+  nextTick(() => {
+    resizeTabChart()
+  })
+}
+defineExpose({resizeTabChart})
+
+watch(queryParams, async () => {
+  crudExpose.doRefresh()
+}, {deep: true})
+
+</script>
+
+<style lang="scss" scoped>
+.campare-switch {
+  flex: none;
+}
+
+::v-deep(.el-table__footer-wrapper td.el-table__cell:nth-child(n+3):nth-child(-n+6) .cell) {
+  display: none;
+}
+
+.en-text {
+  max-width: 100%;
+  font-size: 13px;
+  font-weight: 420;
+  word-break: break-word;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: normal;
+  display: -webkit-box;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+}
+
+::v-deep(.el-table__footer-wrapper) {
+  border: 0;
+}
+
+::v-deep(.el-table .el-table__footer-wrapper .cell) {
+  font-weight: 600;
+}
+
+.text-range {
+  color: #808184;
+}
+
+.ellipsis {
+  max-width: 100%;
+  font-size: 13px;
+  font-weight: 420;
+  word-break: break-word;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: normal;
+  display: -webkit-box;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+}
+
+.ellipsis-inline {
+  max-width: 100%;
+  font-size: 13px;
+  font-weight: 420;
+  word-break: break-word;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: normal;
+  display: inline;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+}
+
+::v-deep(span.el-link__inner) {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  display: inline;
+}
+
+</style>

+ 102 - 94
src/views/adManage/sp/campaigns/crud.tsx

@@ -1,25 +1,26 @@
-import * as api from './api'
-import {dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet} from '@fast-crud/fast-crud'
-import {inject} from 'vue'
-import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
-import {dynBidStrategyEnum} from '/@/views/adManage/utils/enum.js'
-import {parseQueryParams} from '/@/views/adManage/utils/tools.js'
+import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, dict } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
 import XEUtils from 'xe-utils'
+import * as api from './api'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { dynBidStrategyEnum } from '/@/views/adManage/utils/enum.js'
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js'
+import { InfoFilled } from '@element-plus/icons-vue'
 
-export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
   const pageRequest = async (query: UserPageQuery) => {
     const params = parseQueryParams(context.value)
     XEUtils.assign(query, params)
     return await api.GetList(query)
   }
-  const editRequest = async ({form, row}: EditReq) => {
+  const editRequest = async ({ form, row }: EditReq) => {
     form.id = row.id
     return await api.UpdateObj(form)
   }
-  const delRequest = async ({row}: DelReq) => {
+  const delRequest = async ({ row }: DelReq) => {
     return await api.DelObj(row.id)
   }
-  const addRequest = async ({form}: AddReq) => {
+  const addRequest = async ({ form }: AddReq) => {
     return await api.AddObj(form)
   }
 
@@ -40,39 +41,37 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
           borderBottom: '0.5px solid #ddd',
         },
         showSummary: true,
-        stripe: false
+        stripe: false,
       },
       container: {
-        fixedHeight: false
+        fixedHeight: false,
       },
       actionbar: {
         show: true,
         buttons: {
           add: {
-            show: false
+            show: false,
           },
           create: {
             text: '新建广告活动',
             type: 'primary',
             show: true,
-            click() {
-
-            }
-          }
-        }
+            click() {},
+          },
+        },
       },
       search: {
-        show: false
+        show: false,
       },
       toolbar: {
         buttons: {
           search: {
-            show: true
+            show: true,
           },
           compact: {
-            show: false
-          }
-        }
+            show: false,
+          },
+        },
       },
       request: {
         pageRequest,
@@ -90,11 +89,11 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
           edit: {
             iconRight: 'Edit',
             type: 'text',
-            text: null
+            text: null,
             // show: hasPermissions('dictionary:Update'),
           },
           remove: {
-            show: false
+            show: false,
             // iconRight: 'Delete',
             // type: 'text',
             // text: null
@@ -106,11 +105,11 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
         id: {
           title: 'ID',
           column: {
-            show: false
+            show: false,
           },
           form: {
-            show: false
-          }
+            show: false,
+          },
         },
         campaignName: {
           title: '广告活动名称',
@@ -120,62 +119,67 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
             align: 'left',
             renderHeader() {
               return (
-                  <span>
-                <el-tooltip placement="top" content="广告活动是一组使用共同广告预算的广告组。广告组用于组织商品(广告),以便您实现所需的业务目标。">
-            <span>
-                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
-            </span>
-            </el-tooltip>
-            <span>广告活动名称</span>
-            </span>
+                <span>
+                  <el-tooltip
+                    placement="top"
+                    content="广告活动是一组使用共同广告预算的广告组。广告组用于组织商品(广告),以便您实现所需的业务目标。"
+                  >
+                    <span>
+                      <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;">
+                        <InfoFilled />
+                      </el-icon>
+                    </span>
+                  </el-tooltip>
+                  <span>广告活动名称</span>
+                </span>
               )
-            }
+            },
           },
           search: {
             show: true,
             component: {
               props: {
-                clearable: true
-              }
-            }
+                clearable: true,
+              },
+            },
           },
           form: {
-            rules: [{required: true, message: '必填项'}]
-          }
+            rules: [{ required: true, message: '必填项' }],
+          },
         },
         targetingType: {
           title: '投放类型',
           column: {
             width: '100px',
-            align: 'center'
+            align: 'center',
           },
           type: 'dict-select',
           search: {
-            show: true
+            show: true,
           },
           dict: dict({
             data: [
-              {value: 'AUTO', label: '自动'},
-              {value: 'MANUAL', label: '手动'},
-            ]
-          })
+              { value: 'AUTO', label: '自动' },
+              { value: 'MANUAL', label: '手动' },
+            ],
+          }),
         },
         state: {
           title: '状态',
           column: {
             width: '90px',
-            align: 'center'
+            align: 'center',
           },
           type: 'dict-select',
           search: {
-            show: true
+            show: true,
           },
           dict: dict({
             data: [
-              {value: 'PAUSED', label: '已暂停', color: 'warning'},
-              {value: 'ENABLED', label: '投放中', color: 'success'},
-            ]
-          })
+              { value: 'PAUSED', label: '已暂停', color: 'warning' },
+              { value: 'ENABLED', label: '投放中', color: 'success' },
+            ],
+          }),
         },
         dynBidStrategy: {
           title: '竞价策略',
@@ -183,18 +187,18 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
             show: false,
           },
           column: {
-            width: '160px'
+            width: '160px',
           },
           type: 'dict-select',
           dict: dict({
-            data: dynBidStrategyEnum
-          })
+            data: dynBidStrategyEnum,
+          }),
         },
         startDate: {
           title: '开始日期',
           column: {
             width: '100px',
-            align: 'center'
+            align: 'center',
           },
         },
         endDate: {
@@ -208,15 +212,15 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
               } else {
                 return '--'
               }
-            }
+            },
           },
         },
         portfolioName: {
           title: '广告组合',
           column: {
             width: '130px',
-            align: 'center'
-          }
+            align: 'center',
+          },
         },
         dailyBudget: {
           title: '预算',
@@ -229,18 +233,18 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
               } else {
                 return '--'
               }
-            }
-          }
+            },
+          },
         },
         suggestedBudget: {
           title: '建议预算',
           form: {
-            show: false
+            show: false,
           },
           column: {
             width: '100px',
-            align: 'center'
-          }
+            align: 'center',
+          },
         },
         percentTimeInBudget: {
           title: '预算活跃时间均值',
@@ -248,63 +252,67 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
             minWidth: 160,
             renderHeader() {
               return (
-                  <span>
-                <el-tooltip placement="top" content="预算覆盖每天投放时长占比的平均值。">
-            <span>
-                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
-            </span>
-            </el-tooltip>
-            <span>预算活跃时间均值</span>
-            </span>
+                <span>
+                  <el-tooltip placement="top" content="预算覆盖每天投放时长占比的平均值。">
+                    <span>
+                      <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;">
+                        <InfoFilled />
+                      </el-icon>
+                    </span>
+                  </el-tooltip>
+                  <span>预算活跃时间均值</span>
+                </span>
               )
-            }
+            },
           },
           form: {
-            show: false
-          }
+            show: false,
+          },
         },
         MissedImpressions: {
           title: '预计错过的曝光',
           form: {
-            show: false
+            show: false,
           },
           column: {
             width: 180,
             align: 'center',
             renderHeader() {
               return (
-                  <span>
-                <el-tooltip placement="top" content="平均每笔订单的花费,系统计算,花费/广告订单量">
-            <span>
-                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
-            </span>
-            </el-tooltip>
-            <span>订单成本</span>
-            </span>
+                <span>
+                  <el-tooltip placement="top" content="平均每笔订单的花费,系统计算,花费/广告订单量">
+                    <span>
+                      <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;">
+                        <InfoFilled />
+                      </el-icon>
+                    </span>
+                  </el-tooltip>
+                  <span>预计错过的曝光</span>
+                </span>
               )
-            }
+            },
           },
         },
         MissedClicks: {
           title: '预计错过的点击',
           form: {
-            show: false
+            show: false,
           },
           column: {
-            width: 180
+            width: 180,
           },
         },
         MissedSales: {
           title: '预计错过的销售',
           form: {
-            show: false
+            show: false,
           },
           column: {
-            width: 180
+            width: 180,
           },
         },
-        ...BaseColumn
-      }
-    }
+        ...BaseColumn,
+      },
+    },
   }
 }

+ 46 - 33
src/views/adManage/sp/campaigns/index.vue

@@ -5,16 +5,17 @@
         <el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
           <el-tab-pane label="数据趋势" name="dataTendency">
             <DataTendencyChart
-                v-if="tabActiveName === 'dataTendency'"
-                :query="queryParams"
-                :fetchCard="getCardData"
-                :fetchLine="getLineData"
-                :fetch-line-month="getLineMonthData"
-                :fetch-line-week="getLineWeekData">
+              v-if="tabActiveName === 'dataTendency'"
+              :query="queryParams"
+              :fetchCard="getCardData"
+              :fetchLine="getLineData"
+              :fetch-line-month="getLineMonthData"
+              :fetch-line-week="getLineWeekData"
+            >
             </DataTendencyChart>
           </el-tab-pane>
           <el-tab-pane label="广告结构" name="adStruct">
-            <AdStructChart v-if="tabActiveName === 'adStruct'"/>
+            <AdStructChart v-if="tabActiveName === 'adStruct'" />
           </el-tab-pane>
           <el-tab-pane label="散点视图" name="scatterView">
             <div v-if="tabActiveName === 'scatterView'">散点视图</div>
@@ -22,7 +23,7 @@
         </el-tabs>
       </template>
       <template #cell_percentTimeInBudget="scope">
-        <el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0"/>
+        <el-progress :percentage="scope.row.percentTimeInBudget > 0 ? scope.row.percentTimeInBudget * 100 : 0" />
       </template>
       <template #cell_campaignName="scope">
         <el-tooltip effect="dark" :content="scope.row.campaignName" placement="top">
@@ -39,17 +40,18 @@
 
       <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
         <DataCompare
-            :field="field"
-            :value="scope.row[field]"
-            :prev-val="scope.row[`prev${field}`]"
-            :gap-val="scope.row[`gap${field}`]"
-            :date-range="dateRange"
-            :show-compare="showCompare"/>
+          :field="field"
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]"
+          :date-range="dateRange"
+          :show-compare="showCompare"
+        />
       </template>
       <template #toolbar-left>
         <div class="campare-switch">
           <span>数据对比 </span>
-          <el-switch v-model="showCompare" size="small"/>
+          <el-switch v-model="showCompare" size="small" />
         </div>
       </template>
     </fs-crud>
@@ -57,35 +59,35 @@
 </template>
 
 <script lang="ts" setup>
-import {onMounted, ref, watch} from 'vue'
-import {FsPage, useFs} from '@fast-crud/fast-crud'
-import {createCrudOptions} from './crud'
-import {useShopInfo} from '/@/stores/shopInfo'
-import {usePublicData} from '/@/stores/publicData'
-import {storeToRefs} from 'pinia'
-import {useRouter} from 'vue-router'
+import { onMounted, ref, watch } from 'vue'
+import { FsPage, useFs } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { useRouter } from 'vue-router'
 import AdStructChart from './chartComponents/adStruct.vue'
 import DataTendencyChart from '/@/views/adManage/sp/chartComponents/dataTendency.vue'
-import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
-import {BaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import { getCardData, getLineData, getLineMonthData, getLineWeekData } from './api'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
 import DataCompare from '/@/components/dataCompare/index.vue'
 
 const tabActiveName = ref('dataTendency')
 const shopInfo = useShopInfo()
 const publicData = usePublicData()
-const {dateRange} = storeToRefs(publicData)
-const {profile} = storeToRefs(shopInfo)
+const { dateRange } = storeToRefs(publicData)
+const { profile } = storeToRefs(shopInfo)
 const queryParams = ref({
   profileId: profile.value.profile_id,
-  dateRange
+  dateRange,
 })
-const {crudBinding, crudRef, crudExpose} = useFs({createCrudOptions, context: queryParams})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
 const router = useRouter()
 const showCompare = ref(false)
 const jumpGroup = (row: any) => {
   router.push({
     name: 'SpCampaignDetail',
-    query: {campaignId: row.campaignId, tagsViewName: row.campaignName},
+    query: { campaignId: row.campaignId, tagsViewName: row.campaignName },
   })
 }
 
@@ -93,10 +95,21 @@ onMounted(async () => {
   crudExpose.doRefresh()
 })
 
-watch(queryParams, async () => {
-  crudExpose.doRefresh()
-}, {deep: true})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
 
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
 </script>
 
 <style lang="scss" scoped>
@@ -104,7 +117,7 @@ watch(queryParams, async () => {
   flex: none;
 }
 
-::v-deep(.el-table__footer-wrapper td.el-table__cell:nth-child(n+3):nth-child(-n+10) .cell) {
+::v-deep(.el-table__footer-wrapper td.el-table__cell:nth-child(n + 3):nth-child(-n + 10) .cell) {
   display: none;
 }
 

+ 6 - 6
src/views/adManage/sp/searchTerm/crud.tsx

@@ -5,7 +5,7 @@ import * as api from './api'
 import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
 import { parseQueryParams } from '/@/views/adManage/utils/tools.js'
 
-export const createCrudOptions = function ({crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
   const pageRequest = async (query: UserPageQuery) => {
     const params = parseQueryParams(context.value)
     XEUtils.assign(query, params)
@@ -30,7 +30,7 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
           borderBottom: '0.5px solid #ddd',
         },
         showSummary: true,
-        stripe: false
+        stripe: false,
       },
       container: {
         fixedHeight: false,
@@ -62,14 +62,14 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
           title: '搜索词',
           column: {
             width: 130,
-            fixed: 'left'
-          }
+            fixed: 'left',
+          },
         },
         matchType: {
           title: '定向',
           column: {
-            width: 90
-          }
+            width: 90,
+          },
         },
         campaignName: {
           title: '广告活动',