Преглед на файлове

新增SB数据趋势和广告结构页面;更改部分样式

WanGxC преди 1 година
родител
ревизия
36879b9968
променени са 66 файла, в които са добавени 4018 реда и са изтрити 1669 реда
  1. 4 4
      src/components/dataCompare/index.vue
  2. 333 286
      src/theme/app.scss
  3. 1 1
      src/views/adManage/ad-overview/daily/crud.tsx
  4. 6 0
      src/views/adManage/ad-overview/daily/index.vue
  5. 1 1
      src/views/adManage/ad-overview/hourly/crud.tsx
  6. 6 0
      src/views/adManage/ad-overview/hourly/index.vue
  7. 1 1
      src/views/adManage/ad-overview/monthly/crud.tsx
  8. 6 0
      src/views/adManage/ad-overview/monthly/index.vue
  9. 4 1
      src/views/adManage/ad-overview/total/index.vue
  10. 1 1
      src/views/adManage/ad-overview/weekly/crud.tsx
  11. 6 0
      src/views/adManage/ad-overview/weekly/index.vue
  12. 0 112
      src/views/adManage/sb/adPlacement/index.vue
  13. 0 25
      src/views/adManage/sb/campaigns/CreateCampaigns/adFormat/CommoditySet.vue
  14. 1 256
      src/views/adManage/sb/campaigns/CreateCampaigns/index.vue
  15. 49 7
      src/views/adManage/sb/campaigns/api.ts
  16. 20 3
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/ads/api.ts
  17. 32 33
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/ads/crud.tsx
  18. 73 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/ads/index.vue
  19. 12 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/api.ts
  20. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/autoTarget/api.ts
  21. 95 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/autoTarget/crud.tsx
  22. 67 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/autoTarget/index.vue
  23. 80 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/index.vue
  24. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/keyword/api.ts
  25. 95 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/keyword/crud.tsx
  26. 67 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/keyword/index.vue
  27. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/manualTarget/api.ts
  28. 88 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/manualTarget/crud.tsx
  29. 74 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/manualTarget/index.vue
  30. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negKeyword/api.ts
  31. 63 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negKeyword/crud.tsx
  32. 35 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negKeyword/index.vue
  33. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negProduct/api.ts
  34. 66 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negProduct/crud.tsx
  35. 35 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negProduct/index.vue
  36. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negTarget/api.ts
  37. 66 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negTarget/crud.tsx
  38. 26 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negTarget/index.vue
  39. 11 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/searchTerm/api.ts
  40. 79 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/searchTerm/crud.tsx
  41. 67 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/searchTerm/index.vue
  42. 57 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/api.ts
  43. 41 39
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/crud.tsx
  44. 67 0
      src/views/adManage/sb/campaigns/campaignDetail/adGroups/index.vue
  45. 14 0
      src/views/adManage/sb/campaigns/campaignDetail/api.ts
  46. 20 0
      src/views/adManage/sb/campaigns/campaignDetail/budget/api.ts
  47. 57 0
      src/views/adManage/sb/campaigns/campaignDetail/budget/crud.tsx
  48. 44 0
      src/views/adManage/sb/campaigns/campaignDetail/budget/index.vue
  49. 116 0
      src/views/adManage/sb/campaigns/campaignDetail/budget/lineChart.vue
  50. 66 0
      src/views/adManage/sb/campaigns/campaignDetail/index.vue
  51. 12 0
      src/views/adManage/sb/campaigns/campaignDetail/placement/api.ts
  52. 73 0
      src/views/adManage/sb/campaigns/campaignDetail/placement/crud.tsx
  53. 84 0
      src/views/adManage/sb/campaigns/campaignDetail/placement/index.vue
  54. 353 0
      src/views/adManage/sb/campaigns/chartComponents/adStruct.vue
  55. 261 0
      src/views/adManage/sb/campaigns/chartComponents/dataTendency.vue
  56. 202 139
      src/views/adManage/sb/campaigns/crud.tsx
  57. 112 85
      src/views/adManage/sb/campaigns/index.vue
  58. 75 70
      src/views/adManage/sb/index.vue
  59. 0 42
      src/views/adManage/sb/keywords/api.ts
  60. 0 116
      src/views/adManage/sb/keywords/index.vue
  61. 0 42
      src/views/adManage/sb/searchTerms/api.ts
  62. 0 114
      src/views/adManage/sb/searchTerms/crud.tsx
  63. 0 112
      src/views/adManage/sb/searchTerms/index.vue
  64. 0 179
      src/views/adManage/utils/commonTabColumn.ts
  65. 541 0
      src/views/adManage/utils/commonTabColumn.tsx
  66. 187 0
      src/views/adManage/utils/enum.ts

+ 4 - 4
src/components/dataCompare/index.vue

@@ -1,12 +1,12 @@
 <template>
-  <p>{{ props.value }}</p>
+  <p>{{ props.value ?? '--' }}</p>
   <el-popover
     effect="dark"
     :width="260">
     <template #reference>
       <p :class="colorClass" v-show="props.showCompare">
         <template v-if="props.gapVal">
-          <el-icon>
+          <el-icon style="display: inline-block; padding-top: 2px">
             <Top v-if="props.gapVal > 0"/>
             <Bottom v-if="props.gapVal < 0"/>
           </el-icon>
@@ -50,7 +50,7 @@ const colorClass = computed(() => {
 .green {
   color: rgb(24, 172, 54)
 }
-.red {  
+.red {
   color: red;
 }
-</style>
+</style>

+ 333 - 286
src/theme/app.scss

@@ -1,326 +1,368 @@
 /* 初始化样式
 ------------------------------- */
 * {
-	margin: 0;
-	padding: 0;
-	box-sizing: border-box;
-	outline: none !important;
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  outline: none !important;
 }
 
 :root {
-	--next-color-white: #ffffff;
-	--next-bg-main-color: #f8f8f8;
-	--next-bg-color: #f5f5ff;
-	--next-border-color-light: #f1f2f3;
-	--next-color-primary-lighter: #ecf5ff;
-	--next-color-success-lighter: #f0f9eb;
-	--next-color-warning-lighter: #fdf6ec;
-	--next-color-danger-lighter: #fef0f0;
-	--next-color-dark-hover: #0000001a;
-	--next-color-menu-hover: rgba(0, 0, 0, 0.2);
-	--next-color-user-hover: rgba(0, 0, 0, 0.04);
-	--next-color-seting-main: #e9eef3;
-	--next-color-seting-aside: #d3dce6;
-	--next-color-seting-header: #b3c0d1;
+  --next-color-white: #ffffff;
+  --next-bg-main-color: #f8f8f8;
+  --next-bg-color: #f5f5ff;
+  --next-border-color-light: #f1f2f3;
+  --next-color-primary-lighter: #ecf5ff;
+  --next-color-success-lighter: #f0f9eb;
+  --next-color-warning-lighter: #fdf6ec;
+  --next-color-danger-lighter: #fef0f0;
+  --next-color-dark-hover: #0000001a;
+  --next-color-menu-hover: rgba(0, 0, 0, 0.2);
+  --next-color-user-hover: rgba(0, 0, 0, 0.04);
+  --next-color-seting-main: #e9eef3;
+  --next-color-seting-aside: #d3dce6;
+  --next-color-seting-header: #b3c0d1;
 }
 
 html,
 body,
 #app {
-	margin: 0;
-	padding: 0;
-	width: 100%;
-	height: 100%;
-	font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
-	font-weight: 400;
-	-webkit-font-smoothing: antialiased;
-	-webkit-tap-highlight-color: transparent;
-	background-color: var(--next-bg-main-color);
-	font-size: 14px;
-	overflow: hidden;
-	position: relative;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
+  font-weight: 400;
+  -webkit-font-smoothing: antialiased;
+  -webkit-tap-highlight-color: transparent;
+  background-color: var(--next-bg-main-color);
+  font-size: 14px;
+  overflow: hidden;
+  position: relative;
 }
 
 /* 主布局样式
 ------------------------------- */
 .layout-container {
-	width: 100%;
-	height: 100%;
-	.layout-pd {
-		padding: 15px !important;
-	}
-	.layout-flex {
-		display: flex;
-		flex-direction: column;
-		flex: 1;
-	}
-	.layout-aside {
-		background: var(--next-bg-menuBar);
-		box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
-		height: inherit;
-		position: relative;
-		z-index: 1;
-		display: flex;
-		flex-direction: column;
-		overflow-x: hidden !important;
-		.el-scrollbar__view {
-			overflow: hidden;
-		}
-	}
-	.layout-header {
-		padding: 0 !important;
-		height: auto !important;
-	}
-	.layout-main {
-		padding: 0 !important;
-		overflow: hidden;
-		width: 100%;
-		background-color: var(--next-bg-main-color);
-		display: flex;
-		flex-direction: column;
-		// 内层 el-scrollbar样式,用于界面高度自适应(main.vue)
-		.layout-main-scroll {
-			@extend .layout-flex;
-			.layout-parent {
-				@extend .layout-flex;
-				position: relative;
-			}
-		}
-	}
-	// 用于界面高度自适应
-	.layout-padding {
-		@extend .layout-pd;
-		position: absolute;
-		left: 0;
-		top: 0;
-		height: 100%;
-		overflow: hidden;
-		@extend .layout-flex;
-		&-auto {
-			height: inherit;
-			@extend .layout-flex;
-		}
-		&-view {
-			background: var(--el-color-white);
-			width: 100%;
-			height: 100%;
-			border-radius: 4px;
-			border: 1px solid var(--el-border-color-light, #ebeef5);
-			overflow: hidden;
-		}
-	}
-	// 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
-	.layout-padding-unset {
-		padding: 0 !important;
-		&-view {
-			border-radius: 0 !important;
-			border: none !important;
-		}
-	}
-	// 用于设置 iframe loading 时的高度(loading 垂直居中显示)
-	.layout-iframe {
-		.el-loading-parent--relative {
-			height: 100%;
-		}
-	}
-	.el-scrollbar {
-		width: 100%;
-	}
-	.layout-el-aside-br-color {
-		border-right: 1px solid var(--el-border-color-light, #ebeef5);
-	}
-	// pc端左侧导航样式
-	.layout-aside-pc-220 {
-		width: 220px !important;
-		transition: width 0.3s ease;
-	}
-	.layout-aside-pc-64 {
-		width: 64px !important;
-		transition: width 0.3s ease;
-	}
-	.layout-aside-pc-1 {
-		width: 1px !important;
-		transition: width 0.3s ease;
-	}
-	// 手机端左侧导航样式
-	.layout-aside-mobile {
-		position: fixed;
-		top: 0;
-		left: -220px;
-		width: 220px;
-		z-index: 9999999;
-	}
-	.layout-aside-mobile-close {
-		left: -220px;
-		transition: all 0.3s cubic-bezier(0.39, 0.58, 0.57, 1);
-	}
-	.layout-aside-mobile-open {
-		left: 0;
-		transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
-	}
-	.layout-aside-mobile-mode {
-		position: fixed;
-		top: 0;
-		right: 0;
-		bottom: 0;
-		left: 0;
-		height: 100%;
-		background-color: rgba(0, 0, 0, 0.5);
-		z-index: 9999998;
-		animation: error-img 0.3s;
-	}
-	.layout-mian-height-50 {
-		height: calc(100vh - 50px);
-	}
-	.layout-columns-warp {
-		flex: 1;
-		display: flex;
-		overflow: hidden;
-	}
-	.layout-hide {
-		display: none;
-	}
+  width: 100%;
+  height: 100%;
+
+  .layout-pd {
+    padding: 15px !important;
+  }
+
+  .layout-flex {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+  }
+
+  .layout-aside {
+    background: var(--next-bg-menuBar);
+    box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
+    height: inherit;
+    position: relative;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    overflow-x: hidden !important;
+
+    .el-scrollbar__view {
+      overflow: hidden;
+    }
+  }
+
+  .layout-header {
+    padding: 0 !important;
+    height: auto !important;
+  }
+
+  .layout-main {
+    padding: 0 !important;
+    overflow: hidden;
+    width: 100%;
+    background-color: var(--next-bg-main-color);
+    display: flex;
+    flex-direction: column;
+    // 内层 el-scrollbar样式,用于界面高度自适应(main.vue)
+    .layout-main-scroll {
+      @extend .layout-flex;
+
+      .layout-parent {
+        @extend .layout-flex;
+        position: relative;
+      }
+    }
+  }
+
+  // 用于界面高度自适应
+  .layout-padding {
+    @extend .layout-pd;
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    overflow: hidden;
+    @extend .layout-flex;
+
+    &-auto {
+      height: inherit;
+      @extend .layout-flex;
+    }
+
+    &-view {
+      background: var(--el-color-white);
+      width: 100%;
+      height: 100%;
+      border-radius: 4px;
+      border: 1px solid var(--el-border-color-light, #ebeef5);
+      overflow: hidden;
+    }
+  }
+
+  // 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
+  .layout-padding-unset {
+    padding: 0 !important;
+
+    &-view {
+      border-radius: 0 !important;
+      border: none !important;
+    }
+  }
+
+  // 用于设置 iframe loading 时的高度(loading 垂直居中显示)
+  .layout-iframe {
+    .el-loading-parent--relative {
+      height: 100%;
+    }
+  }
+
+  .el-scrollbar {
+    width: 100%;
+  }
+
+  .layout-el-aside-br-color {
+    border-right: 1px solid var(--el-border-color-light, #ebeef5);
+  }
+
+  // pc端左侧导航样式
+  .layout-aside-pc-220 {
+    width: 220px !important;
+    transition: width 0.3s ease;
+  }
+
+  .layout-aside-pc-64 {
+    width: 64px !important;
+    transition: width 0.3s ease;
+  }
+
+  .layout-aside-pc-1 {
+    width: 1px !important;
+    transition: width 0.3s ease;
+  }
+
+  // 手机端左侧导航样式
+  .layout-aside-mobile {
+    position: fixed;
+    top: 0;
+    left: -220px;
+    width: 220px;
+    z-index: 9999999;
+  }
+
+  .layout-aside-mobile-close {
+    left: -220px;
+    transition: all 0.3s cubic-bezier(0.39, 0.58, 0.57, 1);
+  }
+
+  .layout-aside-mobile-open {
+    left: 0;
+    transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
+  }
+
+  .layout-aside-mobile-mode {
+    position: fixed;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5);
+    z-index: 9999998;
+    animation: error-img 0.3s;
+  }
+
+  .layout-mian-height-50 {
+    height: calc(100vh - 50px);
+  }
+
+  .layout-columns-warp {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+  }
+
+  .layout-hide {
+    display: none;
+  }
 }
 
 /* element plus 全局样式
 ------------------------------- */
 .layout-breadcrumb-seting {
-	.el-divider {
-		background-color: rgb(230, 230, 230);
-	}
+  .el-divider {
+    background-color: rgb(230, 230, 230);
+  }
 }
 
 /* nprogress 进度条跟随主题颜色
 ------------------------------- */
 #nprogress {
-	.bar {
-		background: var(--el-color-primary) !important;
-		z-index: 9999999 !important;
-	}
+  .bar {
+    background: var(--el-color-primary) !important;
+    z-index: 9999999 !important;
+  }
 }
 
 /* flex 弹性布局
 ------------------------------- */
 .flex {
-	display: flex;
+  display: flex;
 }
+
 .flex-auto {
-	flex: 1;
-	overflow: hidden;
+  flex: 1;
+  overflow: hidden;
 }
+
 .flex-center {
-	@extend .flex;
-	flex-direction: column;
-	width: 100%;
-	overflow: hidden;
+  @extend .flex;
+  flex-direction: column;
+  width: 100%;
+  overflow: hidden;
 }
+
 .flex-margin {
-	margin: auto;
+  margin: auto;
 }
+
 .flex-warp {
-	display: flex;
-	flex-wrap: wrap;
-	align-content: flex-start;
-	margin: 0 -5px;
-	.flex-warp-item {
-		padding: 5px;
-		.flex-warp-item-box {
-			width: 100%;
-			height: 100%;
-		}
-	}
+  display: flex;
+  flex-wrap: wrap;
+  align-content: flex-start;
+  margin: 0 -5px;
+
+  .flex-warp-item {
+    padding: 5px;
+
+    .flex-warp-item-box {
+      width: 100%;
+      height: 100%;
+    }
+  }
 }
 
 /* cursor 鼠标形状
 ------------------------------- */
 // 默认
 .cursor-default {
-	cursor: default !important;
+  cursor: default !important;
 }
+
 // 帮助
 .cursor-help {
-	cursor: help !important;
+  cursor: help !important;
 }
+
 // 手指
 .cursor-pointer {
-	cursor: pointer !important;
+  cursor: pointer !important;
 }
+
 // 移动
 .cursor-move {
-	cursor: move !important;
+  cursor: move !important;
 }
 
 /* 宽高 100%
 ------------------------------- */
 .w100 {
-	width: 100% !important;
+  width: 100% !important;
 }
+
 .h100 {
-	height: 100% !important;
+  height: 100% !important;
 }
+
 .vh100 {
-	height: 100vh !important;
+  height: 100vh !important;
 }
+
 .max100vh {
-	max-height: 100vh !important;
+  max-height: 100vh !important;
 }
+
 .min100vh {
-	min-height: 100vh !important;
+  min-height: 100vh !important;
 }
 
 /* 颜色值
 ------------------------------- */
 .color-primary {
-	color: var(--el-color-primary);
+  color: var(--el-color-primary);
 }
+
 .color-success {
-	color: var(--el-color-success);
+  color: var(--el-color-success);
 }
+
 .color-warning {
-	color: var(--el-color-warning);
+  color: var(--el-color-warning);
 }
+
 .color-danger {
-	color: var(--el-color-danger);
+  color: var(--el-color-danger);
 }
+
 .color-info {
-	color: var(--el-color-info);
+  color: var(--el-color-info);
 }
 
 /* 字体大小全局样式
 ------------------------------- */
 @for $i from 10 through 32 {
-	.font#{$i} {
-		font-size: #{$i}px !important;
-	}
+  .font#{$i} {
+    font-size: #{$i}px !important;
+  }
 }
 
 /* 外边距、内边距全局样式
 ------------------------------- */
 @for $i from 1 through 35 {
-	.mt#{$i} {
-		margin-top: #{$i}px !important;
-	}
-	.mr#{$i} {
-		margin-right: #{$i}px !important;
-	}
-	.mb#{$i} {
-		margin-bottom: #{$i}px !important;
-	}
-	.ml#{$i} {
-		margin-left: #{$i}px !important;
-	}
-	.pt#{$i} {
-		padding-top: #{$i}px !important;
-	}
-	.pr#{$i} {
-		padding-right: #{$i}px !important;
-	}
-	.pb#{$i} {
-		padding-bottom: #{$i}px !important;
-	}
-	.pl#{$i} {
-		padding-left: #{$i}px !important;
-	}
+  .mt#{$i} {
+    margin-top: #{$i}px !important;
+  }
+  .mr#{$i} {
+    margin-right: #{$i}px !important;
+  }
+  .mb#{$i} {
+    margin-bottom: #{$i}px !important;
+  }
+  .ml#{$i} {
+    margin-left: #{$i}px !important;
+  }
+  .pt#{$i} {
+    padding-top: #{$i}px !important;
+  }
+  .pr#{$i} {
+    padding-right: #{$i}px !important;
+  }
+  .pb#{$i} {
+    padding-bottom: #{$i}px !important;
+  }
+  .pl#{$i} {
+    padding-left: #{$i}px !important;
+  }
 }
 
 // 自定义全局样式
@@ -329,11 +371,11 @@ body,
 }
 
 .fs-page-custom {
-	position: initial !important;
+  position: initial !important;
 
-	.fs-search-col >* {
-		margin-left: 0 !important;
-	}
+  .fs-search-col > * {
+    margin-left: 0 !important;
+  }
 }
 
 .asj-tabs {
@@ -347,29 +389,31 @@ body,
   display: flex;
   height: 40px;
   gap: 24px;
+  border-radius: 10px;
+  overflow: hidden;
 }
 
 .asj-tab {
-	display: flex;
-	align-items: center;
-	padding: 0 4px;
-	height: 40px;
-	font-weight: 700;
-	line-height: 40px;
-	cursor: pointer;
-	border-bottom: 2px solid #fff;
+  display: flex;
+  align-items: center;
+  padding: 0 4px;
+  height: 40px;
+  font-weight: 700;
+  line-height: 40px;
+  cursor: pointer;
+  border-bottom: 2px solid #fff;
 }
 
 .asj-tab.active {
-	color: #409eff;
-	border-color: #409eff
+  color: #409eff;
+  border-color: #409eff
 }
 
 // 详细页里面的样式
 .asj-detail-header {
   position: sticky;
   background-color: #fff;
-  box-shadow: 0px 0px 12px rgba(51,89,181,0.16);
+  box-shadow: 0px 0px 12px rgba(51, 89, 181, 0.16);
   z-index: 10;
   top: 0;
   height: 80px;
@@ -377,17 +421,19 @@ body,
   display: flex;
   flex-direction: column;
 }
+
 .asj-detail-info {
   margin: 5px;
   display: flex;
   flex-direction: row;
   gap: 30px;
 }
+
 .asj-detail-tabs > .el-tabs__header.is-top {
   position: sticky;
   top: 80px;
   z-index: 10;
-	border-top: 1px solid #aeafb0;
+  border-top: 1px solid #aeafb0;
 }
 
 // 顶层公共搜索样式
@@ -401,57 +447,58 @@ body,
   z-index: 10;
   width: 100%;
   background-color: #f8f8f8;
-  box-shadow: 0px 0px 0px rgba(51,89,181,0.16);
+  box-shadow: 0px 0px 0px rgba(51, 89, 181, 0.16);
 
-	.el-input__wrapper {
-		border-radius: 0;
-	}
+  .el-input__wrapper {
+    border-radius: 0;
+  }
 }
 
 .chart-tabs {
-	margin: 5px 0;
-	.el-tabs__nav {
-		padding-left: 0 !important;
-	}
+  margin: 5px 0;
+
+  .el-tabs__nav {
+    padding-left: 0 !important;
+  }
 }
 
 .overview-tabs {
-	display: flex;
-	gap: 8px;
-	padding-bottom: 8px;
-	position: sticky;
-	top: 40px;
-	padding-top: 8px;
-	z-index: 10;
-	width: 100%;
-	background-color: #f8f8f8;
-	box-shadow: 0px 0px 0px rgba(51,89,181,0.16);
-
-	.el-input__wrapper {
-		border-radius: 0;
-	}
+  display: flex;
+  gap: 8px;
+  padding-bottom: 8px;
+  position: sticky;
+  top: 40px;
+  padding-top: 8px;
+  z-index: 10;
+  width: 100%;
+  background-color: #f8f8f8;
+  box-shadow: 0px 0px 0px rgba(51, 89, 181, 0.16);
+
+  .el-input__wrapper {
+    border-radius: 0;
+  }
 }
 
 .overview-top {
-	background-color: #fff;
-	position: sticky;
-	top: 0px;
-	z-index: 9;
-	box-shadow: 0px 0px 12px rgba(51, 89, 181, 0.16);
+  background-color: #fff;
+  position: sticky;
+  top: 0px;
+  z-index: 9;
+  box-shadow: 0px 0px 12px rgba(51, 89, 181, 0.16);
 
-	padding: 0 12px;
-	display: flex;
-	height: 40px;
-	gap: 24px;
+  padding: 0 12px;
+  display: flex;
+  height: 40px;
+  gap: 24px;
 }
 
 .el-table__footer td.el-table__cell {
-	//border: none!important;
-	//border-top: none!important;
-	//margin-top: -1px!important;
-	border: none!important;
-	border-bottom: 0.5px solid rgb(211, 211, 211) !important;
-	//margin-top: 2px!important;
+  //border: none!important;
+  //border-top: none!important;
+  //margin-top: -1px!important;
+  border: none !important;
+  border-bottom: 0.5px solid rgb(211, 211, 211) !important;
+  //margin-top: 2px!important;
 }
 
 //.el-table tr:last-child td {

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

@@ -31,7 +31,7 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
         height: 800,
         showSummary: true,
         headerCellStyle: {
-          backgroundColor: '#f8f8f9', // 直接设置背景颜色
+          backgroundColor: '#f6f7fa', // 直接设置背景颜色
           // border: '0.5px solid #ddd',
         },
         // rowClassName() {

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

@@ -122,4 +122,10 @@ watch(queryParams, async () => {
 .campare-switch {
   flex: none;
 }
+::v-deep(.el-table--border .el-table__footer-wrapper) {
+  border: none;
+}
+::v-deep(.el-table .el-table__footer-wrapper .cell) {
+  font-weight: 600;
+}
 </style>

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

@@ -31,7 +31,7 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
         height: 800,
         showSummary: true,
         headerCellStyle: {
-          backgroundColor: '#f8f8f9', // 直接设置背景颜色
+          backgroundColor: '#f6f7fa', // 直接设置背景颜色
           // border: '0.5px solid #ddd',
         },
         cellStyle:  {

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

@@ -126,4 +126,10 @@ watch(queryParams, async () => {
 .campare-switch {
 	flex: none;
 }
+::v-deep(.el-table--border .el-table__footer-wrapper) {
+  border: none;
+}
+::v-deep(.el-table .el-table__footer-wrapper .cell) {
+  font-weight: 600;
+}
 </style>

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

@@ -31,7 +31,7 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
         height: 800,
         showSummary: true,
         headerCellStyle: {
-          backgroundColor: '#f8f8f9', // 直接设置背景颜色
+          backgroundColor: '#f6f7fa', // 直接设置背景颜色
           // border: '0.5px solid #ddd',
         },
         cellStyle:  {

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

@@ -122,4 +122,10 @@ watch(queryParams, async () => {
 .campare-switch {
   flex: none;
 }
+::v-deep(.el-table--border .el-table__footer-wrapper) {
+  border: none;
+}
+::v-deep(.el-table .el-table__footer-wrapper .cell) {
+  font-weight: 600;
+}
 </style>

+ 4 - 1
src/views/adManage/ad-overview/total/index.vue

@@ -43,7 +43,7 @@
                       <Top/> <!-- num2 不是负数时显示向上箭头 -->
                     </template>
                   </el-icon>
-                  <span class="ml5 font16" :style="{ color: String(v.num2).includes('-') ? '#59b939' : '#e36f53' }">{{ v.num2 }}%</span>
+                  <span class="l-indent" :style="{ color: String(v.num2).includes('-') ? '#59b939' : '#e36f53' }">{{ v.num2 }}%</span>
                 </div>
                 <div class="home-card-item-icon flex" :style="{ background: `var(${v.color2})` }">
                   <i class="flex-margin font32" :class="v.num4" :style="{ color: `var(${v.color3})` }"></i>
@@ -908,4 +908,7 @@ $homeNavLengh: 8;
 .down {
   margin-top: 10px;
 }
+.l-indent {
+  margin-left: 1px;
+}
 </style>

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

@@ -31,7 +31,7 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
         height: 800,
         showSummary: true,
         headerCellStyle: {
-          backgroundColor: '#f8f8f9', // 直接设置背景颜色
+          backgroundColor: '#f6f7fa', // 直接设置背景颜色
           // border: '0.5px solid #ddd',
         },
         cellStyle:  {

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

@@ -122,4 +122,10 @@ watch(queryParams, async () => {
 .campare-switch {
   flex: none;
 }
+::v-deep(.el-table--border .el-table__footer-wrapper) {
+  border: none;
+}
+::v-deep(.el-table .el-table__footer-wrapper .cell) {
+  font-weight: 600;
+}
 </style>

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

@@ -1,112 +0,0 @@
-<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 - 25
src/views/adManage/sb/campaigns/CreateCampaigns/adFormat/CommoditySet.vue

@@ -1,25 +0,0 @@
-<template>
-    <div class="container" style="margin-top: 20px">
-        <p style="font-weight: bold">需要帮助创建图片或品牌旗舰店?</p>
-        <p style="color: dodgerblue">深入了解广告创意服务(链接未完成)</p>
-        <div>
-            <el-radio-group v-model="radio1">
-                <el-radio label="1" size="large" border>
-                    Option A
-                </el-radio>
-                <el-radio label="2" size="large" border>Option B</el-radio>
-            </el-radio-group>
-        </div>
-    </div>
-</template>
-
-<script lang="ts" setup>
-
-import { ref } from 'vue'
-const radio1 = ref('1')
-
-</script>
-
-<style scoped>
-
-</style>

+ 1 - 256
src/views/adManage/sb/campaigns/CreateCampaigns/index.vue

@@ -1,264 +1,9 @@
 <template>
-    <!--<p>新建广告页面</p>-->
-    <div>
-        <!-- 设置 -->
-        <el-card style="margin: 5px">
-            <div style="font-size: 19px; font-weight: bold"><span style="background-color: rgb(53, 105, 214); margin-right: 5px">|</span>设置</div>
-            <div class="container">
-                <el-form
-                        ref="ruleFormRef"
-                        :model="ruleForm"
-                        :rules="rules"
-                        label-width="120px"
-                        class="demo-ruleForm"
-                        :size="formSize"
-                        status-icon
-                        :label-position="labelPosition"
-                >
-                    <el-form-item class="form-item" label="广告活动名称" prop="name" style="width: 50%;">
-                        <el-input v-model="ruleForm.name"/>
-                    </el-form-item>
-                    <el-form-item class="form-item" label="广告组合" prop="portfolios" style="margin-left: 9px">
-                        <el-select v-model="ruleForm.portfolios" placeholder="请选择" style="margin-left: -9px">
-                            <el-option label="Zone one" value="shanghai"/>
-                            <el-option label="Zone two" value="beijing"/>
-                        </el-select>
-                    </el-form-item>
-                    <el-form-item class="form-item" label="预算" prop="budget">
-                        <el-select-v2
-                                v-model="ruleForm.budget"
-                                placeholder="Activity budget"
-                                :options="options"
-                        />
-                    </el-form-item>
-                    <el-form-item class="form-item" label="活动时间" required>
-                        <el-col :span="11">
-                            <el-form-item prop="date1">
-                                <el-date-picker
-                                        v-model="ruleForm.date1"
-                                        type="date"
-                                        label="Pick a date"
-                                        placeholder="开始时间"
-                                        style="width: 100%"
-                                />
-                            </el-form-item>
-                        </el-col>
-                        <el-col class="text-center" style="margin-bottom: 22px !important;" :span="1">
-                            <span class="text-center">—</span>
-                        </el-col>
-                        <el-col :span="11">
-                            <el-form-item prop="date2">
-                                <el-date-picker
-                                        v-model="ruleForm.date2"
-                                        type="date"
-                                        label="Pick a date"
-                                        placeholder="结束时间"
-                                        style="width: 100%"
-                                />
-                            </el-form-item>
-                        </el-col>
-                    </el-form-item>
-                    <el-form-item class="form-item" label="自动竞价" prop="delivery" @click.prevent style="margin-left: 9px">
-                        <el-switch v-model="ruleForm.delivery" style="margin-left: -9px"/>
-                        <span style="margin-left: 15px; color: #7a7a7a">允许亚马逊自动优化搜索结果首页以外的广告位竞价</span>
-                    </el-form-item>
-                    <el-form-item>
-                        <el-button type="primary" @click="submitForm(ruleFormRef)">
-                            Create
-                        </el-button>
-                        <el-button @click="resetForm(ruleFormRef)">Reset</el-button>
-                    </el-form-item>
-                </el-form>
-            </div>
-        </el-card>
-        <!-- 广告组 -->
-        <el-card style="margin: 5px">
-            <div style="font-size: 19px; font-weight: bold">
-                <span style="background-color: rgb(53, 105, 214); margin-right: 5px">|</span>
-                广告组
-            </div>
-            <div>
-                <el-form ref="ruleFormRef"
-                           :model="ruleForm"
-                           :rules="rules"
-                           label-width="120px"
-                           class="demo-ruleForm"
-                           :size="formSize"
-                           status-icon
-                           :label-position="labelPosition">
-                    <el-form-item class="form-item" label="广告组名称" prop="groupName" style="width: 50%;">
-                        <el-input v-model="ruleForm.groupName"/>
-                    </el-form-item>
-                </el-form>
-            </div>
-        </el-card>
-        <!-- 广告格式 -->
-        <el-card style="margin: 5px">
-            <div style="font-size: 19px; font-weight: bold">
-                <span style="background-color: rgb(53, 105, 214); margin-right: 5px">|</span>
-                广告格式
-            </div>
-            <div>
-                <el-form ref="ruleFormRef"
-                         :model="ruleForm"
-                         :rules="rules"
-                         label-width="120px"
-                         class="demo-ruleForm"
-                         :size="formSize"
-                         status-icon
-                         :label-position="labelPosition">
-                </el-form>
-            </div>
-            <el-tabs>
-                <el-tab-pane label="商品集"><CommoditySet/></el-tab-pane>
-                <el-tab-pane label="品牌旗舰店焦点">品牌旗舰店焦点</el-tab-pane>
-                <el-tab-pane label="视频">视频</el-tab-pane>
-            </el-tabs>
-        </el-card>
-    </div>
+  <p>新建广告页面</p>
 </template>
 
 <script lang="ts" setup>
-import {reactive, ref} from 'vue'
-import type {FormInstance, FormRules, FormProps} from 'element-plus'
-import CommoditySet from './adFormat/CommoditySet.vue'
-
-interface RuleForm {
-    name: string
-    portfolios: string
-    budget: string
-    date1: string
-    date2: string
-    delivery: boolean
-    type: string[]
-    resource: string
-    desc: string
-    groupName: string
-}
-
-const formSize = ref('default')
-const ruleFormRef = ref<FormInstance>()
-const ruleForm = reactive<RuleForm>({
-    name: '',
-    portfolios: '',
-    budget: '',
-    date1: '',
-    date2: '',
-    delivery: false,
-    type: [],
-    resource: '',
-    desc: '',
-    groupName: '',
-})
-
-const rules = reactive<FormRules<RuleForm>>({
-    name: [
-        {required: true, message: 'Please input Activity name', trigger: 'blur'},
-        {min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur'},
-    ],
-    portfolios: [
-        {
-            required: false,
-            message: 'Please select Activity zone',
-            trigger: 'change',
-        },
-    ],
-    budget: [
-        {
-            required: true,
-            message: 'Please select Activity budget',
-            trigger: 'change',
-        },
-    ],
-    date1: [
-        {
-            type: 'date',
-            required: true,
-            message: 'Please pick a date',
-            trigger: 'change',
-        },
-    ],
-    date2: [
-        {
-            type: 'date',
-            required: false,
-            message: 'Please pick a time',
-            trigger: 'change',
-        },
-    ],
-    type: [
-        {
-            type: 'array',
-            required: true,
-            message: 'Please select at least one activity type',
-            trigger: 'change',
-        },
-    ],
-    resource: [
-        {
-            required: true,
-            message: 'Please select activity resource',
-            trigger: 'change',
-        },
-    ],
-    desc: [
-        {required: true, message: 'Please input activity form', trigger: 'blur'},
-    ],
-    groupName: [
-        {required: true, message: 'Please input Group Name', trigger: 'blur'},
-    ]
-})
-
-const submitForm = async (formEl: FormInstance | undefined) => {
-    if (!formEl) return
-    await formEl.validate((valid, fields) => {
-        if (valid) {
-            console.log('submit!')
-            console.log(ruleForm)
-        } else {
-            console.log('error submit!', fields)
-        }
-    })
-}
-
-const resetForm = (formEl: FormInstance | undefined) => {
-    if (!formEl) return
-    formEl.resetFields()
-}
-
-const options = Array.from({length: 10000}).map((_, idx) => ({
-    value: `${idx + 1}`,
-    label: `${idx + 1}`,
-}))
-
-const labelPosition = ref<FormProps['labelPosition']>('left')
-
 </script>
 
 <style scoped>
-    .form-item :deep(.el-form-item__label) {
-        font-weight: bold;
-    }
-    :deep(.el-tabs__nav-scroll) {
-        display: flex;
-        justify-content: space-around;
-    }
-    :deep([id^="tab"]) {
-        padding: 0;
-        margin-right: 230px;
-        border: 1px solid #D3D3D3;
-        border-radius: 5px;
-    }
-    :deep(div#tab-0) {
-        padding: 15px;
-    }
-    :deep(div#tab-1) {
-        padding: 15px;
-    }
-    :deep(div#tab-2) {
-        padding: 15px;
-    }
-    :deep(.el-tabs__nav-wrap::after) {
-        width: 0;
-    }
 </style>

+ 49 - 7
src/views/adManage/sb/campaigns/api.ts

@@ -1,18 +1,17 @@
 import { request } from '/@/utils/service';
-import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-import XEUtils from 'xe-utils';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
 
-export const apiPrefix = 'api/ad_manage/sbcampaigns/';
-export function GetList(query: PageQuery) {
+export const apiPrefix = '/api/ad_manage/sbcampaigns/';
+export function GetList(query: UserPageQuery) {
     return request({
-        url: apiPrefix,
+        url: apiPrefix + 'list/',
         method: 'get',
         params: query,
     })
 }
-export function GetObj(id: InfoReq) {
+export function GetObj(id: any) {
     return request({
-        url: apiPrefix + id,
+        url: apiPrefix + id + "/",
         method: 'get',
     });
 }
@@ -40,3 +39,46 @@ export function DelObj(id: DelReq) {
         data: { id },
     });
 }
+
+export function getCardData(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + "total/",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineData(query: UserPageQuery) {
+    query["dateRangeType"] = "D"
+    return request({
+        url: apiPrefix + "daily/",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineWeekData(query: UserPageQuery) {
+    query["dateRangeType"] = "W"
+    return request({
+        url: apiPrefix + "daily/",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getLineMonthData(query: UserPageQuery) {
+    query["dateRangeType"] = "M"
+    return request({
+        url: apiPrefix + "daily/",
+        method: 'GET',
+        params: query
+    })
+}
+
+export function getAdStructureData(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + "structure/",
+        method: 'GET',
+        params: query
+    })
+}

+ 20 - 3
src/views/adManage/sb/adPlacement/api.ts → src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/ads/api.ts

@@ -1,11 +1,10 @@
 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 const apiPrefix = '/api/ad_manage/spgroupdetail/';
 export function GetList(query: UserPageQuery) {
     return request({
-        url: apiPrefix,
+        url: apiPrefix + 'ads/list/',
         method: 'get',
         params: query,
     })
@@ -40,3 +39,21 @@ export function DelObj(id: DelReq) {
         data: { id },
     });
 }
+
+
+export function getCardData(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + "total/",
+        method: 'GET',
+        params: query
+    })
+  }
+  
+  export function getLineData(query: UserPageQuery) {
+    query["dateRangeType"] = "D"
+    return request({
+      url: apiPrefix + "daily/",
+      method: 'GET',
+      params: query
+    })
+  }

+ 32 - 33
src/views/adManage/sb/keywords/crud.tsx → src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/ads/crud.tsx

@@ -1,11 +1,14 @@
 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 { inject } from 'vue'
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
 import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
 
-
 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) => {
@@ -24,14 +27,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 
 	return {
 		crudOptions: {
-			table: {
-				height: 600
-			},
+			table: {},
 			container: {
         fixedHeight: false
       },
 			actionbar: {
-				show: true,
+				show: false,
 				buttons: {
 					add: {
 						show: false
@@ -39,7 +40,15 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 				}
 			},
 			search: {
-				show: false
+				show: true,
+				buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
 			},
 			toolbar: {
         buttons: {
@@ -79,35 +88,25 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 				},
 			},
 			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: '状态'
+        asin: {
+					title: 'asin',
+					column: {
+						width: "120px"
+					}
 				},
-				bid: {
-					title: '出价'
+				sku: {
+					title:'sku',
+					column: {
+						width: "180px"
+					}
 				},
-				"campaign": {
-					title: '广告活动'
+				state: {
+					title: '状态',
+					column: {
+						width: "100px"
+					}
 				},
-				"adGroup": {
-					title: '广告组'
-				}
+				...BaseColumn
 			}
 		}
 	}

+ 73 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/ads/index.vue

@@ -0,0 +1,73 @@
+<template>
+	<fs-page class="fs-page-custom">
+		<fs-crud ref="crudRef" v-bind="crudBinding">
+			<template #search-left>
+				<DateRangePicker v-model="dateRange"></DateRangePicker>
+			</template>
+			<template #header-middle>
+				<DataTendencyChart :query="queryParams" :fetch-card="getCardData" :fetch-line="getLineData"> </DataTendencyChart>
+			</template>
+			<template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+          :field="field" 
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]" 
+          :date-range="dateRange"
+          :show-compare="showCompare"/>
+      </template>
+      <template #toolbar-left>
+        <div>
+          <span>数据对比 </span>
+          <el-switch v-model="showCompare" size="small" />
+        </div>
+      </template>
+		</fs-crud>
+	</fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, watch } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+import { LocationQueryValue } from 'vue-router'
+import DataTendencyChart from '/@/views/adManage/sp/chartComponents/dataTendency.vue'
+// import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { getCardData, getLineData } from './api'
+import { storeToRefs } from 'pinia'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+
+
+interface Props {
+	adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+const showCompare = ref(false)
+const queryParams = ref({
+	adGroupId: props.adGroupId,
+	dateRange,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+
+onMounted(() => {
+	crudExpose.doRefresh()
+})
+watch(queryParams, async () => {
+	crudExpose.doRefresh()
+}, { deep: true })
+</script>
+
+<style lang="scss">
+.chart-tabs {
+	margin: 5px 0;
+
+	.el-tabs__nav {
+		padding-left: 0 !important;
+	}
+}
+</style>

+ 12 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/api.ts

@@ -0,0 +1,12 @@
+import { request } from '/@/utils/service'
+import { LocationQueryValue } from 'vue-router'
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/'
+
+export function GetObj(adGroupId: LocationQueryValue | LocationQueryValue[]) {
+	return request({
+		url: apiPrefix + 'head/',
+		method: 'get',
+		params: { adGroupId },
+	})
+}

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/autoTarget/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/targets/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 95 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/autoTarget/crud.tsx

@@ -0,0 +1,95 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { 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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: true,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: true,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: false,
+      },
+      columns: {
+        keyword: {
+          title: '目标群体',
+          column: {
+            width: '200px',
+            fixed: 'left',
+            align: 'center',
+          },
+        },
+        state: {
+          title: '状态',
+          column: {
+            width: '100px',
+            align: 'center',
+          },
+        },
+        suggestedBid: {
+          title: '建议竞价',
+          column: {
+            width: '100px',
+            align: 'center',
+          },
+        },
+        bid: {
+          title: '出价',
+          column: {
+            width: '60px',
+            align: 'center',
+          },
+        },
+        // currentBid: {
+        //   title: '当前竞价',
+        //   column: {
+        //     width: '100px',
+        //     align: 'center',
+        //   },
+        // },
+        ...BaseColumn,
+      },
+    },
+  }
+}

+ 67 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/autoTarget/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+          :field="field"
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]"
+          :date-range="dateRange"
+          :show-compare="showCompare"
+        />
+      </template>
+      <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 { ref, onMounted, computed, watch, onBeforeMount } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+// import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { LocationQueryValue } from 'vue-router'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+// const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+// const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  adGroupId: props.adGroupId,
+  dateRange,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const showCompare = ref(false)
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped></style>

+ 80 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/index.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="asj-container">
+    <div class="asj-detail-header">
+      <span style="font-size: x-large; font-weight: bold; color: #0f1111;margin: 5px;"> 
+        <span> {{ adGroupInfo.adGroupName }} </span>
+      </span>
+      <div class="asj-detail-info">
+        <span>状态:{{ adGroupInfo.state }}</span>
+        <span>投放类型:{{ adGroupInfo.targetingType }}</span>
+        <span>默认竞价:{{ profile.currency_symbol + adGroupInfo.defaultBid }}</span>
+        <span>投放日期:{{ adGroupInfo.startDate }} ~ {{ adGroupInfo.endDate ?? '无结束日期' }}</span>
+      </div>
+    </div>
+    <el-tabs type="border-card" class="asj-detail-tabs" v-model="tabActiveName">
+      <el-tab-pane label="推广商品" name="adProducts">
+        <Ads v-if="tabActiveName==='adProducts'" :adGroupId="route.query.adGroupId"></Ads>
+      </el-tab-pane>
+      <template v-if="route.query.targetingType === 'automatic'">
+        <el-tab-pane label="定向" name="tab2">
+          <AutoTarget v-if="tabActiveName === 'tab2'" :adGroupId="route.query.adGroupId">定向</AutoTarget>
+        </el-tab-pane>
+        <el-tab-pane label="否定投放" name="tab3">
+          <NegTarget v-if="tabActiveName === 'tab3'" :adGroupId="route.query.adGroupId">否定投放</NegTarget>
+        </el-tab-pane>
+      </template>
+      <template v-else-if="route.query.targetingType ==='product'">
+        <el-tab-pane label="商品投放" name="tab2">
+          <ManualTarget v-if="tabActiveName === 'tab2'" :adGroupId="route.query.adGroupId">商品投放</ManualTarget>
+        </el-tab-pane>
+        <el-tab-pane label="否定商品" name="tab3">
+          <NegProduct v-if="tabActiveName === 'tab3'" :adGroupId="route.query.adGroupId">否定商品</NegProduct>
+        </el-tab-pane>
+      </template>
+      <template v-else>
+        <el-tab-pane label="关键词" name="tab2">
+          <Keyword v-if="tabActiveName === 'tab2'" :ad-group-id="route.query.adGroupId">关键词</Keyword>
+        </el-tab-pane>
+        <el-tab-pane label="否定词" name="tab3">
+          <NegKeyword v-if="tabActiveName === 'tab3'" :ad-group-id="route.query.adGroupId">否定词</NegKeyword>
+        </el-tab-pane>
+      </template>
+      <el-tab-pane label="搜索关键词" name="searchTerm">
+        <SearchTerm v-if="tabActiveName === 'searchTerm'" :adGroupId="route.query.adGroupId" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { Ref, ref, onMounted, computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { GetObj } from './api'
+import { useShopInfo } from '/@/stores/shopInfo'
+// import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import Ads from './ads/index.vue'
+import SearchTerm from './searchTerm/index.vue'
+import Keyword from './keyword/index.vue'
+import AutoTarget from './autoTarget/index.vue'
+import ManualTarget from './manualTarget/index.vue'
+import NegProduct from './negProduct/index.vue'
+import NegKeyword from './negKeyword/index.vue'
+import NegTarget from './negTarget/index.vue'
+
+const tabActiveName = ref('adProducts')
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+const route = useRoute()
+const adGroupInfo: Ref<SpAdGroup> = ref({})
+
+onMounted(async () => {
+  const resp = await GetObj(route.query.adGroupId)
+  adGroupInfo.value = resp.data
+})
+
+</script>
+
+<style scoped>
+
+</style>

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/keyword/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/targets/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 95 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/keyword/crud.tsx

@@ -0,0 +1,95 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { 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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: false,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: true,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: false,
+      },
+      columns: {
+        keywordText: {
+          title: '关键词',
+          column: {
+            width: '200px',
+            fixed: 'left',
+            align: 'center',
+          },
+        },
+        state: {
+          title: '状态',
+          column: {
+            width: '100px',
+            align: 'center',
+          },
+        },
+        suggestedBid: {
+          title: '建议竞价',
+          column: {
+            width: '100px',
+            align: 'center',
+          },
+        },
+        bid: {
+          title: '出价',
+          column: {
+            width: '60px',
+            align: 'center',
+          },
+        },
+        // currentBid: {
+        //   title: '当前竞价',
+        //   column: {
+        //     width: '100px',
+        //     align: 'center',
+        //   },
+        // },
+        ...BaseColumn,
+      },
+    },
+  }
+}

+ 67 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/keyword/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+          :field="field"
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]"
+          :date-range="dateRange"
+          :show-compare="showCompare"
+        />
+      </template>
+      <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 { ref, onMounted, computed, watch, onBeforeMount } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+// import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { LocationQueryValue } from 'vue-router'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+// const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+// const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  adGroupId: props.adGroupId,
+  dateRange,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const showCompare = ref(false)
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped></style>

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/manualTarget/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/targets/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 88 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/manualTarget/crud.tsx

@@ -0,0 +1,88 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { 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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: false,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: true,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: false,
+      },
+      columns: {
+        resolvedExpression: {
+          title: '商品',
+          column: {
+            width: '300px',
+            fixed: 'left',
+            align: 'center',
+          },
+        },
+        state: {
+          title: '状态',
+          column: {
+            width: '100px',
+            align: 'center',
+          },
+        },
+        suggestedBid: {
+          title: '建议竞价',
+          column: {
+            width: '100px',
+            align: 'center',
+          },
+        },
+        bid: {
+          title: '出价',
+          column: {
+            width: '60px',
+            align: 'center',
+          },
+        },
+        ...BaseColumn,
+      },
+    },
+  }
+}

+ 74 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/manualTarget/index.vue

@@ -0,0 +1,74 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+          :field="field"
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]"
+          :date-range="dateRange"
+          :show-compare="showCompare"
+        />
+      </template>
+      <template #toolbar-left>
+        <div class="campare-switch">
+          <span>数据对比 </span>
+          <el-switch v-model="showCompare" size="small" />
+        </div>
+      </template>
+
+      <template #cell_resolvedExpression="scope">
+        <p v-for="[key, val] of Object.entries(scope.row.resolvedExpression)">
+          {{ TargetExpressionEnum.find((item) => item.value === key)?.label }}:{{ val }}
+        </p>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, computed, watch, onBeforeMount } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+// import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { LocationQueryValue } from 'vue-router'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+import { TargetExpressionEnum } from '/@/views/adManage/utils/enum'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+// const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+// const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  adGroupId: props.adGroupId,
+  dateRange,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const showCompare = ref(false)
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped></style>

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negKeyword/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/negativekeyword/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 63 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negKeyword/crud.tsx

@@ -0,0 +1,63 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: false,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: false,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: true,
+      },
+      columns: {
+        keywordText: {
+          title: '否定词',
+        },
+        matchType: {
+          title: '匹配类型',
+        },
+      },
+    },
+  }
+}

+ 35 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negKeyword/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, computed, watch, onBeforeMount } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+import { LocationQueryValue } from 'vue-router'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const queryParams = ref({
+  adGroupId: props.adGroupId,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped></style>

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negProduct/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/negativetarget/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 66 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negProduct/crud.tsx

@@ -0,0 +1,66 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: false,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: false,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: false,
+      },
+      columns: {
+        resolvedExpression_type: {
+          title: '否定类型',
+        },
+        product: {
+          title: '商品和品牌',
+        },
+        resolvedExpression_value: {
+          title: 'SKU/ASIN',
+        },
+      },
+    },
+  }
+}

+ 35 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negProduct/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, computed, watch, onBeforeMount } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+import { LocationQueryValue } from 'vue-router'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const queryParams = ref({
+  adGroupId: props.adGroupId,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped></style>

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negTarget/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/negativetarget/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 66 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negTarget/crud.tsx

@@ -0,0 +1,66 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: false,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: false,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: false,
+      },
+      columns: {
+        resolvedExpression_type: {
+          title: '否定类型',
+        },
+        product: {
+          title: '商品和品牌',
+        },
+        resolvedExpression_value: {
+          title: 'SKU/ASIN',
+        },
+      },
+    },
+  }
+}

+ 26 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/negTarget/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <el-tabs v-model="tabActiveName">
+    <el-tab-pane label="否定商品" name="negProduct">
+      <NegProduct v-if="tabActiveName === 'negProduct'" :ad-group-id="props.adGroupId"/>
+    </el-tab-pane>
+    <el-tab-pane label="否定词" name="negKeyword">
+      <NegKeyword v-if="tabActiveName === 'negKeyword'" :ad-group-id="props.adGroupId"/>
+    </el-tab-pane>
+  </el-tabs>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue'
+import { LocationQueryValue } from 'vue-router'
+import NegProduct from '../negProduct/index.vue'
+import NegKeyword from '../negKeyword/index.vue'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const tabActiveName = ref('negProduct')
+
+</script>
+
+<style scoped></style>

+ 11 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/searchTerm/api.ts

@@ -0,0 +1,11 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+
+export const apiPrefix = '/api/ad_manage/spgroupdetail/searchterm/';
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix + 'list/',
+        method: 'get',
+        params: query,
+    })
+}

+ 79 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/searchTerm/crud.tsx

@@ -0,0 +1,79 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { inject } from 'vue'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { 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 hasPermissions = inject('$hasPermissions')
+
+  return {
+    crudOptions: {
+      table: {},
+      container: {
+        fixedHeight: false,
+      },
+      actionbar: {
+        show: false,
+      },
+      search: {
+        show: true,
+        buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+      },
+      // toolbar: {
+      //   buttons: {
+      //     search: {
+      //       show: true,
+      //     },
+      //     compact: {
+      //       show: false,
+      //     },
+      //   },
+      // },
+      request: {
+        pageRequest,
+      },
+      rowHandle: {
+        show: false,
+      },
+      columns: {
+        searchTerm: {
+          title: '搜索词',
+          column: {
+            width: '200px',
+            fixed: 'left',
+          },
+        },
+        targeting: {
+          title: '定向',
+          column: {
+            width: '200px'
+          },
+        },
+        matchType: {
+          title: '匹配类型',
+          column: {
+            width: '100px',
+            align: 'center'
+          },
+        },
+        ...BaseColumn,
+      },
+    },
+  }
+}

+ 67 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/adGroupDetail/searchTerm/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+          :field="field"
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]"
+          :date-range="dateRange"
+          :show-compare="showCompare"
+        />
+      </template>
+      <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 { ref, onMounted, computed, watch, onBeforeMount } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+// import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { LocationQueryValue } from 'vue-router'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+interface Props {
+  adGroupId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+// const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+// const { profile } = storeToRefs(shopInfo)
+const queryParams = ref({
+  adGroupId: props.adGroupId,
+  dateRange,
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+const showCompare = ref(false)
+
+onMounted(async () => {
+  crudExpose.doRefresh()
+})
+watch(
+  queryParams,
+  async () => {
+    crudExpose.doRefresh()
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped></style>

+ 57 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/api.ts

@@ -0,0 +1,57 @@
+import { request } from '/@/utils/service'
+import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud'
+
+export const apiPrefix = '/api/ad_manage/spcampaigndetail/spgroups/'
+export function GetList(query: UserPageQuery) {
+	return request({
+		url: apiPrefix + 'list/',
+		method: 'get',
+		params: query,
+	})
+}
+export function GetObj(id: number) {
+	return request({
+		url: apiPrefix + id + '/',
+		method: 'get',
+	})
+}
+
+export function AddObj(obj: AddReq) {
+	return request({
+		url: apiPrefix,
+		method: 'post',
+		data: obj,
+	})
+}
+
+export function UpdateObj(obj: EditReq) {
+	return request({
+		url: apiPrefix + obj.id + '/',
+		method: 'put',
+		data: obj,
+	})
+}
+
+export function DelObj(id: DelReq) {
+	return request({
+		url: apiPrefix + id + '/',
+		method: 'delete',
+		data: { id },
+	})
+}
+
+export function getCardData(query: UserPageQuery) {
+	return request({
+		url: apiPrefix + 'total/',
+		method: 'GET',
+		params: query,
+	})
+}
+
+export function getLineData(query: UserPageQuery) {
+	return request({
+		url: apiPrefix + 'daily/',
+		method: 'GET',
+		params: query,
+	})
+}

+ 41 - 39
src/views/adManage/sb/adPlacement/crud.tsx → src/views/adManage/sb/campaigns/campaignDetail/adGroups/crud.tsx

@@ -1,11 +1,14 @@
-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 * 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 { 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) => {
@@ -25,21 +28,16 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 	return {
 		crudOptions: {
 			table: {
-				height: 600
+				height: 750
 			},
 			container: {
         fixedHeight: false
       },
-			actionbar: {
-				show: true,
-				buttons: {
-					add: {
-						show: false
-					}
-				}
-			},
-			search: {
-				show: false
+			request: {
+				pageRequest,
+				addRequest,
+				editRequest,
+				delRequest,
 			},
 			toolbar: {
         buttons: {
@@ -51,12 +49,6 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 					}
 				}
 			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
 			rowHandle: {
 				fixed: 'right',
 				width: 80,
@@ -79,35 +71,45 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 				},
 			},
 			columns: {
-        keywordText: {
-          title: '关键词'
+        adGroupName: {
+          title: '广告组名称',
+          column: {
+            width: '150px'
+          },
+					search: {
+						show: true,
+						component: {
+							props: {
+								clearable: true
+							}
+						}
+					},
+					form: {
+						rules: [{required: true, message:'必填项'}]
+					}
         },
-				matchType: {
-					title: '匹配类型',
+				targetingType: {
+					title: '投放类型',
 					type: 'dict-select',
-					search: {
-						show: true
+					column: {
+						align: 'center',
+						width: '110px'
 					},
 					dict: dict({
 						data: [
-							{ value: 'BROAD', label: '广泛匹配' },
-							{ value: 'PHRASE', label: '词组匹配' },
-							{ value: 'EXACT', label: '精准匹配' },
+							{ label: '自动定向', value: 'automatic' },
+							{ label: '商品定向', value: 'product' },
+							{ label: '关键词投放', value: 'keyword' }
 						]
 					})
 				},
 				state: {
 					title: '状态'
 				},
-				bid: {
-					title: '出价'
-				},
-				"campaign": {
-					title: '广告活动'
+				defaultBid: {
+					title: '默认竞价'
 				},
-				"adGroup": {
-					title: '广告组'
-				}
+				...BaseColumn
 			}
 		}
 	}

+ 67 - 0
src/views/adManage/sb/campaigns/campaignDetail/adGroups/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+      <template #header-middle>
+        <DataTendencyChart
+          :query="queryParams"
+          :fetchCard="getCardData"
+          :fetchLine="getLineData">
+        </DataTendencyChart>
+      </template>
+      <template #cell_adGroupName="scope">
+        <el-link type="primary" :underline="false" @click="jumpAds(scope.row)">{{ scope.row.adGroupName }}</el-link>
+      </template>
+		</fs-crud>
+	</fs-page>
+</template>
+
+<script lang="ts" setup>
+import { Ref, ref, onMounted, watch } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud';
+import { createCrudOptions } from './crud'
+import { useRoute, useRouter, LocationQueryValue } from 'vue-router'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import DataTendencyChart from '/@/views/adManage/sp/chartComponents/dataTendency.vue'
+import { getCardData, getLineData } from './api'
+
+const publicData = usePublicData()
+const router = useRouter()
+interface Props {
+  campaignId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const { dateRange } = storeToRefs(publicData)
+const queryParams = ref({
+  campaignId: props.campaignId,
+  dateRange
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+
+onMounted(() => {
+	crudExpose.doRefresh();
+})
+const jumpAds = (row: any) => {
+  router.push({
+    name: 'AdGroupDetail',
+    query: { adGroupId: row.adGroupId, targetingType: row.targetingType, tagsViewName: row.adGroupName }
+  })
+}
+watch(
+  queryParams,
+  async () => {
+
+    crudExpose.doRefresh() 
+  },
+  { deep: true }
+)
+
+</script>
+
+<style scoped>
+
+</style>

+ 14 - 0
src/views/adManage/sb/campaigns/campaignDetail/api.ts

@@ -0,0 +1,14 @@
+import { request } from '/@/utils/service'
+import { LocationQueryValue } from 'vue-router'
+// import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+// import XEUtils from 'xe-utils';
+
+export const apiPrefix = '/api/ad_manage/spcampaigndetail/'
+
+export function GetObj(campaignId: LocationQueryValue | LocationQueryValue[]) {
+	return request({
+		url: apiPrefix + 'head/',
+		method: 'get',
+		params: { campaignId },
+	})
+}

+ 20 - 0
src/views/adManage/sb/campaigns/campaignDetail/budget/api.ts

@@ -0,0 +1,20 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery } from '@fast-crud/fast-crud';
+
+
+export const apiPrefix = '/api/ad_manage/spcampaigndetail/budget/'
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+
+export function GetLineList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}

+ 57 - 0
src/views/adManage/sb/campaigns/campaignDetail/budget/crud.tsx

@@ -0,0 +1,57 @@
+import * as api from './api'
+import { dict, UserPageQuery, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js'
+import XEUtils from 'xe-utils'
+
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const pageRequest = async (query: UserPageQuery) => {
+		const params = parseQueryParams(context.value)
+		XEUtils.assign(query, params)
+		return await api.GetList(query)
+	}
+
+	return {
+		crudOptions: {
+			table: {
+				// height: 800,
+			},
+			container: {
+				fixedHeight: false,
+			},
+			actionbar: {
+				show: false,
+			},
+			search: {
+				show: true,
+				buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+			},
+			toolbar: {
+				buttons: {
+					search: {
+						show: true,
+					},
+					compact: {
+						show: false,
+					},
+				},
+			},
+			request: {
+				pageRequest,
+			},
+			rowHandle: {
+				show: false,
+			},
+			columns: {
+				
+			},
+		},
+	}
+}

+ 44 - 0
src/views/adManage/sb/campaigns/campaignDetail/budget/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+      <template #header-middle>
+        <LineChart :query="queryParams"/>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { LocationQueryValue } from 'vue-router'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { createCrudOptions } from './crud'
+import LineChart from './lineChart.vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+interface Props {
+  campaignId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const queryParams = ref({
+  campaignId: props.campaignId,
+  dateRange
+})
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: queryParams })
+
+// onMounted(async () => {
+// 	crudExpose.doRefresh()
+// })
+
+</script>
+
+<style scoped>
+
+</style>

+ 116 - 0
src/views/adManage/sb/campaigns/campaignDetail/budget/lineChart.vue

@@ -0,0 +1,116 @@
+<template>
+  <el-card v-loading="loading" shadow="never" style="margin-bottom: 5px; border-radius:0;">
+    <div style="height: 350px;" ref="chartRef"></div>
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { ref,onMounted, onBeforeUnmount, watch, computed } from 'vue'
+import * as echarts from 'echarts'
+import { GetLineList } from './api'
+import { parseQueryParams } from '/@/views/adManage/utils/tools.js'
+
+interface Props {
+  query: any
+}
+const props = defineProps<Props>()
+const chartRef = ref()
+let chartObj:any
+const loading = ref(true)
+const queryParams = computed(() => parseQueryParams(props.query))
+
+const resizeChart = () => { chartObj.resize() }
+const addResize = () => { window.addEventListener('resize', resizeChart) }
+const removeResize = () => { window.removeEventListener('resize', resizeChart) }
+onMounted(() => {
+  addResize()
+  setTimeout(() => { initLine() }, 0)
+})
+onBeforeUnmount(() => {
+	if(chartObj) {
+		chartObj.dispose()
+    chartObj = null
+	}
+  removeResize()
+})
+
+const initLine = async () => {
+  chartObj = echarts.init(chartRef.value)
+  const option = {
+    dataset: {
+      source: []
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    grid: { top: 30, bottom: 30, left: 30, right: 30 },
+    xAxis: {
+      type: 'category'
+    },
+    yAxis: {
+      type: 'value',
+      name: '预算',
+      axisLine: {
+        show: true,
+				lineStyle: { color: '#0085ff' }
+      },
+    },
+    series: [
+      {
+        type: 'line',
+        encode: {
+          x: 'date',
+          y: 'campaignBudgetAmount'
+        },
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 4,
+        lineStyle: { width: 2 },
+        itemStyle: {
+          color: '#0085ff',
+          borderColor: '#0085ff'
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#0085ff93' },
+            { offset: 1, color: '#0085ff03' },
+          ]),
+        },
+      }
+    ]
+  }
+  const items = await getDataset()
+  option.dataset.source = items
+  chartObj.setOption(option)
+  loading.value = false
+}
+const getDataset = async () => {
+  const resp = await GetLineList(queryParams.value)
+  return resp.data
+}
+watch(
+  props.query,
+  async () => {
+    loading.value = true
+
+    const dataset = await getDataset()
+    chartObj.setOption({
+      dataset: {
+        source: dataset
+      }
+    })
+
+    loading.value = false
+  },
+  { deep: true }
+)
+</script>
+
+<style scoped>
+
+</style>

+ 66 - 0
src/views/adManage/sb/campaigns/campaignDetail/index.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="asj-container">
+    <div class="asj-detail-header">
+      <span style="font-size: x-large; font-weight: bold; color: #0f1111;margin: 5px;">
+        <span> {{ campaignInfo.campaignName }}</span>
+      </span>
+      <div class="asj-detail-info">
+        <span>状态:{{ campaignInfo.state }} ({{ campaignInfo.servingStatus }})</span>
+        <span>预算:{{ profile.currency_symbol + campaignInfo.budget }} | {{ campaignInfo.budgetType }}</span>
+        <span>投放类型:{{ campaignInfo.targetingType }}</span>
+        <span>投放日期:{{ campaignInfo.startDate }} ~ {{ campaignInfo.endDate ?? '无结束日期' }}</span>
+        <span>广告组合:{{ campaignInfo?.portfolioName }}</span>
+        <span>竞价策略:{{ getEnumLabel(dynBidStrategyEnum, campaignInfo.dynBidStrategy) }}</span>
+      </div>
+    </div>
+    <el-tabs type="border-card" class="asj-detail-tabs" v-model="tabActiveName">
+      <el-tab-pane label="广告组" name="adGroup">
+        <AdGroups :campaignId="route.query.campaignId" v-if="tabActiveName==='adGroup'"></AdGroups>
+      </el-tab-pane>
+      <el-tab-pane label="预算" name="budget">
+        <Budget :campaignId="route.query.campaignId" v-if="tabActiveName==='budget'"></Budget>
+      </el-tab-pane>
+      <el-tab-pane label="自动化" name="automation">
+        自动化
+      </el-tab-pane>
+      <el-tab-pane label="广告位" name="placement">
+        <Placement :campaignId="route.query.campaignId" v-if="tabActiveName==='placement'"/>
+      </el-tab-pane>
+      <el-tab-pane label="否定投放" name="negative">
+        否定投放
+      </el-tab-pane>
+      <!-- <el-tab-pane label="操作日志" :lazy="true"></el-tab-pane> -->
+    </el-tabs>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, onBeforeMount, Ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import AdGroups from './adGroups/index.vue'
+import Placement from './placement/index.vue'
+import Budget from './budget/index.vue'
+import { getEnumLabel } from '/@/views/adManage/utils/tools.js'
+import { dynBidStrategyEnum, spCampaignServingStatusEnum } from '/@/views/adManage/utils/enum.js'
+import { useShopInfo } from '/@/stores/shopInfo'
+// import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+
+import { GetObj } from './api'
+
+const shopInfo = useShopInfo()
+const { profile } = storeToRefs(shopInfo)
+const route = useRoute()
+const campaignInfo: Ref<SpCampaign> = ref({})
+const tabActiveName = ref('adGroup')
+
+onMounted(async () => {
+  const resp = await GetObj(route.query.campaignId)
+  campaignInfo.value = resp.data
+})
+
+</script>
+
+<style lang="scss">
+
+</style>

+ 12 - 0
src/views/adManage/sb/campaigns/campaignDetail/placement/api.ts

@@ -0,0 +1,12 @@
+import { request } from '/@/utils/service';
+import { UserPageQuery } from '@fast-crud/fast-crud';
+
+
+export const apiPrefix = '/api/ad_manage/spcampaigndetail/placement/'
+export function GetList(query: UserPageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}

+ 73 - 0
src/views/adManage/sb/campaigns/campaignDetail/placement/crud.tsx

@@ -0,0 +1,73 @@
+import { CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { spCampaignPlacementEnum } from '/@/views/adManage/utils/enum'
+import XEUtils from 'xe-utils'
+
+
+export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
+	const { fetchData } = context
+
+	return {
+		crudOptions: {
+			table: {
+			},
+			container: {
+        fixedHeight: false
+      },
+			actionbar: {
+				show: false,
+				buttons: {
+					add: {
+						show: false
+					}
+				}
+			},
+			search: {
+				show: true,
+				buttons: {
+					search: {
+						show: false
+					},
+					reset: {
+						show: false
+					}
+				}
+			},
+			toolbar: {
+        buttons: {
+					search: {
+						show: true
+					},
+					compact: {
+						show: false
+					},
+					refresh: {
+						click: async () => fetchData()
+					}
+				}
+			},
+			pagination: {
+				show: false
+			},
+			rowHandle: {
+        show: false
+      },
+			columns: {
+        placement: {
+          title: '广告位',
+          column: {
+            width: '200px',
+						fixed: 'left',
+						formatter({ value, row, index }){
+								return XEUtils.find(spCampaignPlacementEnum, (item) => item.value === value).label
+						}
+          }
+        },
+				bidAdjustment: {
+					title: '竞价调整'
+				},
+        ...BaseColumn
+			}
+		}
+	}
+}

+ 84 - 0
src/views/adManage/sb/campaigns/campaignDetail/placement/index.vue

@@ -0,0 +1,84 @@
+<template>
+  <fs-page class="fs-page-custom" v-loading='loading'>
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+      <template #search-left>
+        <DateRangePicker v-model="dateRange"></DateRangePicker>
+      </template>
+      <template v-for="field of Object.keys(BaseColumn)" #[`cell_${field}`]="scope">
+        <DataCompare
+          :field="field" 
+          :value="scope.row[field]"
+          :prev-val="scope.row[`prev${field}`]"
+          :gap-val="scope.row[`gap${field}`]" 
+          :date-range="dateRange"
+          :show-compare="showCompare"/>
+      </template>
+      <template #toolbar-left>
+        <div>
+          <span>数据对比 </span>
+          <el-switch v-model="showCompare" size="small" />
+        </div>
+      </template>
+    </fs-crud>
+  </fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, Ref, watch } from 'vue'
+import { useFs, FsPage } from '@fast-crud/fast-crud'
+import { createCrudOptions } from './crud'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+import { GetList } from './api'
+import { BaseColumn } from '/@/views/adManage/utils/commonTabColumn.js'
+import { LocationQueryValue } from 'vue-router'
+import DataCompare from '/@/components/dataCompare/index.vue'
+
+defineOptions({
+  name: "Placement"
+})
+const publicData = usePublicData()
+const { dateRange } = storeToRefs(publicData)
+interface Props {
+  campaignId: LocationQueryValue | LocationQueryValue[]
+}
+const props = defineProps<Props>()
+const loading = ref(true)
+
+const showCompare = ref(false)
+const fetchData = async () => {
+  loading.value = true
+  const resp = await GetList({ 
+    campaignId: props.campaignId,
+    startDate: dateRange.value[0],
+    endDate: dateRange.value[1]
+  })
+  crudExpose.setTableData(resp.data)
+  loading.value = false
+}
+
+const context = { fetchData }
+
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context })
+onMounted(async () => {
+  await fetchData()
+})
+
+
+
+watch(
+  dateRange, 
+  async () => await fetchData()
+)
+
+</script>
+
+<style scoped>
+.red {
+  color: red;
+}
+.green {
+  color: #1cbc0e;
+}
+</style>

+ 353 - 0
src/views/adManage/sb/campaigns/chartComponents/adStruct.vue

@@ -0,0 +1,353 @@
+<template>
+  <div v-loading="loading">
+    <el-row :gutter="5">
+      <el-col :span="8">
+        <div>
+          <!--<TextSelector v-model="modelValue" :options="computedPieOptions" @change="changePie" style="margin-top: 5px"/>-->
+          <el-select v-model="modelValue" class="m-2" size="small" @change="changePie" style="width: 120px">
+            <el-option
+                v-for="item in computedPieOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div ref="pie" style="height: 400px;"></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="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 { computed, inject, onMounted, ref, watch } from "vue"
+import * as echarts from "echarts"
+import TextSelector from '/@/components/TextSelector/index.vue'
+import { getAdStructureData } from "/@/views/adManage/sb/campaigns/api"
+import { createDisabledOptions } from '../../../utils/dropdowndisable'
+import { barOptions1, barOptions2, barOptionsMap, metricMap, pieOptions } from '/@/views/adManage/utils/enum'
+import { useShopInfo } from '/@/stores/shopInfo'
+
+
+const shopInfo = useShopInfo()
+let pieChart = ref()
+let barChart = ref()
+const pie = ref()
+const bar = ref()
+const loading = ref(true)
+
+const dateRange = inject('dateRange')
+
+// 下拉框相关
+let modelValue = ref(pieOptions[0].value)
+let barModelValue1 = ref(barOptions1[0].value)
+let barModelValue2 = ref(barOptions2[2].value)
+
+onMounted(async () => {
+  barChart = echarts.init(bar.value)
+  pieChart = echarts.init(pie.value)
+
+  window.addEventListener('resize', resizeChart)  // 监听窗口大小变化,调整图表大小
+  setTimeout(() => {
+    resizeChart()
+  }, 0)
+
+  await initPieBarData()
+  initChart()
+})
+
+// 获取总数据
+let allData = null
+
+async function setAdStructureData() {
+  allData = await getAdStructureData({ startDate: dateRange.value[0], endDate: dateRange.value[1], profileId: shopInfo.profile.profile_id })
+  console.log('allData.data', allData.data)
+  return allData.data
+}
+
+// 饼图总数据和柱状图总数据
+let pieData
+let barData
+let pieBarData
+// 柱状图初始数据
+let ACOSList
+let SpendList
+let xAxisList
+let xAxisMapList
+
+async function initPieBarData() {
+  pieBarData = await setAdStructureData()
+  pieData = [
+    { value: pieBarData.pie_data[0].Spend, name: '品牌视频' },
+    { value: pieBarData.pie_data[1].Spend, name: '商品集' },
+    { value: pieBarData.pie_data[2].Spend, name: '视频' },
+  ]
+  barData = pieBarData.line_data
+  // 柱状图初始化数据
+  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 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: '商品集' },
+    { value: pieBarData.pie_data[2][flag.value], name: '视频' },
+  ]
+  pieChart.setOption(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()
+    updatePieChartData(resp)
+    updateBarChartData(resp)
+    loading.value = false
+  }
+})
+
+// 根据新数据和当前下拉框选择更新 饼图数据
+function updatePieChartData(resp) {
+  option2.series[0].data = [
+    { value: resp.pie_data[0][modelValue.value], name: '品牌视频' },
+    { value: resp.pie_data[1][modelValue.value], name: '商品集' },
+    { value: resp.pie_data[2][modelValue.value], name: '视频' },
+  ]
+  pieChart.setOption(option2)
+}
+
+// 根据新数据和当前下拉框选择更新 柱状图数据
+function updateBarChartData(resp) {
+  const barValues1 = resp.line_data.map(item => item[barModelValue1.value])
+  const barValues2 = resp.line_data.map(item => item[barModelValue2.value])
+
+  option.series[0].data = barValues1
+  option.series[1].data = barValues2
+  barChart.setOption(option)
+}
+
+const computedBarOptions1 = computed(() => createDisabledOptions(barOptions1, barModelValue2.value, barModelValue1.value))
+const computedBarOptions2 = computed(() => createDisabledOptions(barOptions2, barModelValue1.value, barModelValue2.value))
+const computedPieOptions = computed(() => createDisabledOptions(pieOptions, modelValue.value))
+
+// 初始化图表
+function initChart() {
+  // 柱状图配置
+  option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+      },
+      rich: {
+        b: {
+          color: '#4C5058',
+          fontSize: 15,
+          fontWeight: 'bold',
+          lineHeight: 33
+        },
+      }
+    },
+    toolbox: {
+      feature: {
+        saveAsImage: { yAxisIndex: 'none' }
+      }
+    },
+    grid: { top: 55, right: 60, bottom: 55, left: 55, },
+    xAxis: [
+      {
+        type: 'category',
+        boundaryGap: true,
+        data: xAxisMapList,
+        // axisLabel: {
+        //   rotate: -30, // 将标签旋转
+        //   fontSize: 13
+        // }
+      }
+    ],
+    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: barOptionsMap[barModelValue1.value],
+        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: [6, 6, 6, 6],
+        },
+      },
+      {
+        name: barOptionsMap[barModelValue2.value],
+        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: [6, 6, 6, 6],
+        },
+      },
+    ],
+  }
+  barChart.setOption(option)
+  // 饼图配置
+  option2 = {
+    tooltip: {
+      show: false,
+      trigger: 'item',
+    },
+    series: [
+      {
+        type: 'pie',
+        radius: ['20%', '45%'],
+        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>

+ 261 - 0
src/views/adManage/sb/campaigns/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>

+ 202 - 139
src/views/adManage/sb/campaigns/crud.tsx

@@ -1,150 +1,213 @@
 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'
+import {inject} from 'vue'
+import {BaseColumn, SbBaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import {dynBidStrategyEnum} from '/@/views/adManage/utils/enum.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) => {
-        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 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)
+  }
 
-    // todo 点击新建广告活动进行路由跳转(还有问题)
-    const router = useRouter()
+  //权限判定
+  const hasPermissions = inject('$hasPermissions')
 
-    function goCreate() {
-        router.push('/createcampaigns')
-    }
+  return {
+    crudOptions: {
+      table: {
+        height: 800,
+        // showSummary: true,
+        headerCellStyle: {
+          backgroundColor: '#f6f7fa', // 直接设置背景颜色
+          height: '20px',
+          // border: '0.5px solid #ddd',
+        },
+        cellStyle: {
+          border: 'none',
+          borderBottom: '0.5px solid #ddd',
+        },
+        showSummary: true,
+      },
+      container: {
+        fixedHeight: false
+      },
+      actionbar: {
+        show: true,
+        buttons: {
+          add: {
+            show: false
+          },
+          create: {
+            text: '新建广告活动',
+            type: 'primary',
+            show: true,
+            click() {
 
-    return {
-        crudOptions: {
-            table: {
-                height: 800
-            },
-            container: {
-                fixedHeight: false
-            },
-            actionbar: {
-                show: true,
-                buttons: {
-                    add: {
-                        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
             }
+          }
+        }
+      },
+      search: {
+        show: false
+      },
+      toolbar: {
+        buttons: {
+          search: {
+            show: true
+          },
+          compact: {
+            show: false
+          }
         }
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest,
+      },
+      rowHandle: {
+        fixed: 'right',
+        width: 80,
+        buttons: {
+          view: {
+            show: false,
+          },
+          edit: {
+            iconRight: 'Edit',
+            type: 'text',
+            text: null
+            // show: hasPermissions('dictionary:Update'),
+          },
+          remove: {
+            show: false
+            // iconRight: 'Delete',
+            // type: 'text',
+            // text: null
+            // show: hasPermissions('dictionary:Delete'),
+          },
+        },
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          column: {
+            show: false
+          },
+          form: {
+            show: false
+          }
+        },
+        campaignName: {
+          title: '广告活动名称',
+          sortable: true,
+          column: {
+            fixed: 'left',
+            width: 180,
+            // showOverflowTooltip: true,
+          },
+        },
+        state: {
+          title: '状态',
+          sortable: true,
+          column: {
+            width: '90px',
+            align: 'center'
+          },
+          type: 'dict-select',
+          search: {
+            show: true
+          },
+          dict: dict({
+            data: [
+              {value: 'PAUSED', label: '已暂停', color: 'warning'},
+              {value: 'ENABLED', label: '投放中', color: 'success'},
+            ]
+          })
+        },
+        bidOptimization: {
+          title: '竞价',
+          form: {
+            show: false,
+          },
+          column: {
+            width: '90px',
+            sortable: true,
+            align: 'center',
+            formatter: (row) => {
+              switch (row.value) {
+                case true:
+                  return '自动竞价'
+                case false:
+                  return '自定义竞价'
+                default:
+                  return '-'
+              }
+            }
+          },
+        },
+        startDate: {
+          title: '开始日期',
+          column: {
+            width: '120px',
+            align: 'center',
+            sortable: true,
+          },
+        },
+        endDate: {
+          title: '结束日期',
+          column: {
+            width: '120px',
+            align: 'center',
+            sortable: true,
+            formatter: (row) => {
+              if (row.value !== null) {
+                return row.value
+              } else {
+                return '--'
+              }
+            }
+          },
+        },
+        portfolioName: {
+          title: '广告组合',
+          type: 'text',
+          column: {
+            width: '120px',
+            align: 'center',
+            formatter: (row) => {
+              if (row.value !== null) {
+                return row.value
+              } else {
+                return '--'
+              }
+            }
+          }
+        },
+        dailyBudget: {
+          title: '预算',
+          column: {
+            sortable: true,
+            width: '120px',
+            align: 'right',
+          }
+        },
+        ...SbBaseColumn
+      }
     }
+  }
 }

+ 112 - 85
src/views/adManage/sb/campaigns/index.vue

@@ -1,101 +1,128 @@
 <template>
-    <fs-page class="fs-page-custom">
-        <fs-crud ref="crudRef" v-bind="crudBinding">
-            <template #header-middle>
-                <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" @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" :pieBarChartData="pieBarChartData"/>
-                    </el-tab-pane>
-                    <el-tab-pane label="散点透视">
-                        <!--<ScatterChart ref="scatter"/>-->
-                    </el-tab-pane>
-                </el-tabs>
-            </template>
-        </fs-crud>
-    </fs-page>
-</template>
+	<fs-page class="fs-page-custom">
+		<fs-crud ref="crudRef" v-bind="crudBinding">
+			<template #header-middle>
+				<el-tabs v-model="tabActiveName" class="chart-tabs" type="border-card">
+					<el-tab-pane label="数据趋势" name="dataTendency">
+						<DataTendencyChart
+							v-if="tabActiveName === 'dataTendency'"
+							: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'" />
+					</el-tab-pane>
+					<el-tab-pane label="散点视图" name="scatterView">
+						<div v-if="tabActiveName === 'scatterView'">散点视图</div>
+					</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_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_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>
 
-<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"
+			<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>
 
-const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} })
+<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 AdStructChart from './chartComponents/adStruct.vue'
+import DataTendencyChart from '/@/views/adManage/sp/chartComponents/dataTendency.vue'
+import {getCardData, getLineData, getLineMonthData, getLineWeekData} from './api'
+import {createMul, SbBaseColumn} from '/@/views/adManage/utils/commonTabColumn.js'
+import DataCompare from '/@/components/dataCompare/index.vue'
 
-onMounted(() => {
-    crudExpose.doRefresh()
+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 router = useRouter()
+const showCompare = ref(false)
 
-let barLineData= inject('barLineData')
-let pieBarChartData = inject('pieBarChartData')
-const pieBar = ref()
-const barLine = ref()
-const scatter = ref()
-
-function resizeTabChart() {
-    pieBar.value.resizeChart()
-    barLine.value.resizeChart()
+const jumpGroup = (row: any) => {
+	router.push({
+		name: 'CampaignDetail',
+		query: { campaignId: row.campaignId, tagsViewName: row.campaignName },
+	})
 }
 
-const changeTab = () => {
-    nextTick(() => {
-        resizeTabChart()
-    })
-}
+onMounted( () => {
+  crudExpose.doRefresh()
+})
 
-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%'},
-])
+watch(queryParams, () => {
+	crudExpose.doRefresh()
+}, { deep: true })
 
-function changeMetric() {
-    console.log(metrics.value)
-}
+</script>
 
-// 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]
-    });
+<style lang="scss" scoped>
+.campare-switch {
+	flex: none;
 }
 
-function updateData(newData) {
-    // 更新响应式数据
-    Object.assign(barLineData, newData)
-    // 然后更新图表
-    barLine.value.updateChart()
+::v-deep(.el-table__footer-wrapper td.el-table__cell:nth-child(n+3):nth-child(-n+6) .cell) {
+  display: none;
 }
 
-defineExpose({ resizeTabChart })
-
-</script>
-
-<style scoped>
-
+.en-text {
+  max-width: 100%;
+  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--border .el-table__footer-wrapper) {
+  border: none;
+}
+::v-deep(.el-table .el-table__footer-wrapper .cell) {
+    font-weight: 600;
+}
 </style>

+ 75 - 70
src/views/adManage/sb/index.vue

@@ -1,87 +1,92 @@
 <template>
-    <div class="asj-container">
-        <div class="public-search">
-            <DateRangePicker v-model="dateRange" timezone="America/Los_Angeles" @change="changeDateRange"></DateRangePicker>
-            <el-select v-model="portfolios" placeholder="广告组合"></el-select>
-        </div>
-        <div>
-            <el-tabs class="asj-tabs" @tab-change="changeTab">
-                <el-tab-pane label="广告活动">
-                    <campaigns ref="campaignsRef"/>
-                </el-tab-pane>
-                <el-tab-pane label="关键词" :lazy="true">
-                    <!--<keywords ref="keywordsRef"/>-->
-                </el-tab-pane>
-                <el-tab-pane label="商品投放" :lazy="true">
+  <div class="asj-container">
+    <div class="public-search">
+      <DateRangePicker v-model="dateRange"></DateRangePicker>
+      <el-select
+          v-model="selectedPortfolios"
+          placeholder="广告组合"
+          clearable
+          multiple
+          style="width: 400px"
+          collapse-tags
+          collapse-tags-tooltip
+          :max-collapse-tags="3"
+      >
+        <el-option v-for="info of portfolios" :label="info.name" :value="info.portfolioId"></el-option>
+      </el-select>
+    </div>
 
-                </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-tabs>
-        </div>
+    <div class="asj-tabs">
+      <div v-for="tab of tabs" :key="tab.name" :class="['asj-tab', { active: tabActiveName === tab.name }]" @click="tabActiveName = tab.name">
+        {{ tab.label }}
+      </div>
     </div>
+    <component :is="tabsComponents[tabActiveName]"></component>
+  </div>
 </template>
 
 <script lang="ts" setup>
+import { ref, onBeforeMount, Ref, watch, provide } from 'vue'
+import { useShopInfo } from '/@/stores/shopInfo'
+import { usePublicData } from '/@/stores/publicData'
+import { storeToRefs } from 'pinia'
+import { GetAllPortfolios } from '/@/views/adManage/portfolios/api'
 import DateRangePicker from '/@/components/DateRangePicker/index.vue'
-import campaigns from './campaigns/index.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([])
-
-function changeDateRange(val: string[]) {
-    console.log(val)
-}
-
-const campaignsRef = ref()
-const keywordsRef = ref()
+import Campaigns from '/@/views/adManage/sb/campaigns/index.vue'
+import Keywords from './keywords/index.vue'
+import Targets from './targets/index.vue'
+import SearchTerm from './searchTerm/index.vue'
+import AdvertisedProducts from './advertisedProducts/index.vue'
+import PurchasedOtherProducts from './purchasedOtherProducts/index.vue'
+import Placement from './placement/index.vue'
 
-function changeTab() {
-    nextTick(() => {
-        campaignsRef.value.resizeTabChart()
-        keywordsRef.value.resizeTabChart()
-    })
+// const shopInfo = useShopInfo()
+const publicData = usePublicData()
+const selectedPortfolios: Ref<string[]> = ref([])
+const portfolios: Ref<Portfolio[]> = ref([])
+const { dateRange } = storeToRefs(publicData)
+const tabActiveName = ref('Campaigns')
+const tabs = [
+  { label: '广告活动', name: 'Campaigns' },
+  // { label: '关键词投放', name: 'Keywords' },
+  // { label: '商品投放', name: 'Targets' },
+  // { label: '推广商品', name: 'AdvertisedProducts' },
+  // { label: '购买的其他商品', name: 'PurchasedOtherProducts' },
+  // { label: '搜索词', name: 'SearchTerm' },
+  // { label: '广告位', name: 'Placement' }
+]
+const tabsComponents: any = {
+  Campaigns,
+  // Keywords,
+  // Targets,
+  // AdvertisedProducts,
+  // PurchasedOtherProducts,
+  // SearchTerm,
+  // Placement
 }
 
-// 提供柱线图数据
+provide('dateRange', dateRange)
 
-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]
+onBeforeMount(async () => {
+  const resp: APIResponseData = await GetAllPortfolios()
+  portfolios.value = resp.data
 })
-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>
-.public-search {
-    display: flex;
-    gap: 3px;
-    padding-bottom: 3px;
-    position: sticky;
-    top: 0;
-    z-index: 10;
-    width: 100%;
-    background-color: #f8f8f8;
-    box-shadow: 0 0 0 rgba(51, 89, 181, 0.16);
+<style scoped>
+::v-deep(.el-tabs.el-tabs--top.el-tabs--border-card.chart-tabs) {
+  border-radius: 10px;
+  overflow: hidden;
+}
+.fs-container .box .inner .body {
+  boder-radius: 10px;
+}
+::v-deep(.fs-container .box .inner .body) {
+  border-radius: 10px;
+  flex: 1;
+  padding: 10px;
+  border: 0.5px solid #dddfe6;
+  background-color: white;
 }
 </style>

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

@@ -1,42 +0,0 @@
-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 },
-    });
-}

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

@@ -1,116 +0,0 @@
-<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>

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

@@ -1,42 +0,0 @@
-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 },
-    });
-}

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

@@ -1,114 +0,0 @@
-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: '广告组'
-				}
-			}
-		}
-	}
-}

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

@@ -1,112 +0,0 @@
-<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 - 179
src/views/adManage/utils/commonTabColumn.ts

@@ -1,179 +0,0 @@
-export const BaseColumn = {
-  Impression: {
-    title: '曝光量',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  Click: {
-    title: '点击量',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  CTR: {
-    title: '点击率',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  Spend: {
-    title: '花费',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  CPC: {
-    title: '点击成本',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  TotalPurchases: {
-    title: '订单量',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  TotalUnitOrdered: {
-    title: '销量',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  TotalSales: {
-    title: '销售额',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  PurchasesRate: {
-    title: '转化率',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  ACOS: {
-    title: 'ACOS',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  ROAS: {
-    title: 'ROAS',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  },
-  CPA: {
-    title: '订单成本',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right',
-      minWidth: '125px'
-    }
-  }
-}
-
-export const SpCampaignPuchasedOtherProductsColumn = {
-  TotalPurchasesOtherSKU: {
-    title: '其它商品订单数',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right'
-    }
-  },
-  TotalUnitOrderedOtherSKU: {
-    title: '其它商品销量',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right'
-    }
-  },
-  TotalSalesOtherSKU: {
-    title: '其它商品销售额',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right'
-    }
-  },
-  PurchasesOtherSKURate: {
-    title: '其它商品订单数占比',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right'
-    }
-  },
-  UnitOrderedOtherSKURate: {
-    title: '其它商品销量占比',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right'
-    }
-  },
-  SalesOtherSKURate: {
-    title: '其它商品销售额占比',
-    form: {
-      show: false
-    },
-    column:{
-      align: 'right'
-    }
-  }
-}

+ 541 - 0
src/views/adManage/utils/commonTabColumn.tsx

@@ -0,0 +1,541 @@
+export const BaseColumn = {
+  Impression: {
+    title: '曝光量',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  Click: {
+    title: '点击量',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  CTR: {
+    title: '点击率',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  Spend: {
+    title: '花费',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  CPC: {
+    title: '点击成本',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  TotalPurchases: {
+    title: '订单量',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  TotalUnitOrdered: {
+    title: '销量',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  TotalSales: {
+    title: '销售额',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  PurchasesRate: {
+    title: '转化率',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  ACOS: {
+    title: 'ACOS',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  ROAS: {
+    title: 'ROAS',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  },
+  CPA: {
+    title: '订单成本',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right',
+      minWidth: '125px'
+    }
+  }
+}
+
+export const SpCampaignPuchasedOtherProductsColumn = {
+  TotalPurchasesOtherSKU: {
+    title: '其它商品订单数',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right'
+    }
+  },
+  TotalUnitOrderedOtherSKU: {
+    title: '其它商品销量',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right'
+    }
+  },
+  TotalSalesOtherSKU: {
+    title: '其它商品销售额',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right'
+    }
+  },
+  PurchasesOtherSKURate: {
+    title: '其它商品订单数占比',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right'
+    }
+  },
+  UnitOrderedOtherSKURate: {
+    title: '其它商品销量占比',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right'
+    }
+  },
+  SalesOtherSKURate: {
+    title: '其它商品销售额占比',
+    form: {
+      show: false
+    },
+    column:{
+      align: 'right'
+    }
+  }
+}
+
+export const SbBaseColumn = {
+  Impression: {
+    title: '曝光量',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="来自亚马逊广告API,广告被展示的次数。">
+            <span>
+                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
+            </span>
+            </el-tooltip>
+            <span>曝光量</span>
+            </span>
+      )
+      },
+      formatter: (row) => {
+        if (row.value != null) {
+          return row.value
+        } else {
+          return '--'
+        }
+      }
+    }
+  },
+  TopOfSearchImpressionShare: {
+    title: '搜索结果顶部展示份额',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '220px',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="
+        广告活动获得的搜索结果顶部展示量占其获得的所有符合条件的搜索结果顶部展示量的百分比。<br />
+        符合条件与否由各种因素所决定,包括广告活动状态和投放状态。该指标适用于品牌推广活动和商品推广活动。<br />
+        对于商品推广,该指标提供了搜索结果顶部(首页)的展示量份额。<br />
+        默认展示所选时间范围内展示份额的最高值,如需查看分天的数据趋势,请点击数据详情按钮。" raw-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>
+      )
+      }
+    }
+  },
+  Click: {
+    title: '点击量',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="来自亚马逊广告API,广告被点击的次数。亚马逊系统会在3天内将无效点击去除。">
+            <span>
+                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
+            </span>
+            </el-tooltip>
+            <span>点击量</span>
+            </span>
+      )
+      }
+    }
+  },
+  CTR: {
+    title: '点击率',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      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>
+      )
+      }
+    }
+  },
+  Spend: {
+    title: '花费',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      border: '0.5px solid #ddd',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top"
+        content="来自亚马逊广告API,亚马逊系统会在3天内将无效点击从统计数据中删除,因此过去3天内的花费可能会有所变化">
+            <span>
+                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
+            </span>
+            </el-tooltip>
+            <span>花费</span>
+            </span>
+      )
+      }
+    }
+  },
+  CPC: {
+    title: '点击成本',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '130px',
+      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>
+      )
+      }
+    }
+  },
+  TotalPurchases: {
+    title: '订单数',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="
+        Seller类型店铺:<br />
+        订单数,来自亚马逊广告API。<br />
+        在点击广告后的7天内(商品推广)、14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)售<br />出的广告商品及库存中其他商品的订单数量;<br />
+        亚马逊系统此项数据最多可能延迟12小时更新。因此,“今天”日期范围内的销售数据可能会延迟。<br />
+        付款失败的订单数量和72小时内取消的订单数量将从订单总数中删除。<br />
+        <br />
+        Vendor类型店铺:<br />
+        订单数,来自亚马逊广告API。<br />
+        在点击广告后的14天内(商品推广)、14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)售<br />出的广告商品及库存中其他商品的订单数量;<br />
+        亚马逊系统此项数据最多可能延迟12小时更新。因此,“今天”日期范围内的销售数据可能会延迟。<br />
+        付款失败的订单数量和72小时内取消的订单数量将从订单总数中删除" raw-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>
+      )
+      }
+    }
+  },
+  TotalSales: {
+    title: '销售额',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="
+        Seller类型店铺:<br />
+        销售额,来自亚马逊广告API。<br />
+        在点击广告后的7天内(商品推广)、14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)售<br/>出的广告商品及库存中其他商品的销售额;<br />
+        亚马逊系统此项数据最多可能延迟12小时更新。因此,“今天”日期范围内的销售数据可能会延迟。<br />
+        付款失败的订单数量和72小时内取消的订单产生的销售额将从总销售额中删除。<br />
+        <br />
+        Vendor类型店铺:<br />
+        销售额,来自亚马逊广告API。<br />
+        在点击广告后的14天内(商品推广)、14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)售<br />出的广告商品及库存中其他商品的销售额;<br />
+        亚马逊系统此项数据最多可能延迟12小时更新。因此,“今天”日期范围内的销售数据可能会延迟。<br />
+        付款失败的订单数量和72小时内取消的订单产生的销售额将从总销售额中删除。" raw-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>
+      )
+      }
+    }
+  },
+  TotalUnitOrdered: {
+    title: '销量',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="
+        Seller类型店铺:<br />
+        销售件数,来自亚马逊广告API。<br />
+        在点击广告后的7天内(商品推广)、14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)售<br />出的广告商品及库存中其他商品的件数;<br />
+        亚马逊系统此项数据最多可能延迟12小时更新。因此,“今天”日期范围内的销售数据可能会延迟。<br />
+        付款失败的订单和72小时内取消的订单产生的销售件数将从销量总数中删除。<br />
+        <br />
+        Vendor类型店铺:<br />
+        销售件数,来自亚马逊广告API。<br />
+        在点击广告后的14天内(商品推广)、14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)售<br />出的广告商品及库存中其他商品的件数;<br />
+        亚马逊系统此项数据最多可能延迟12小时更新。因此,“今天”日期范围内的销售数据可能会延迟。<br />
+        付款失败的订单和72小时内取消的订单产生的销售件数将从销量总数中删除。" raw-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>
+      )
+      }
+    }
+  },
+  PurchasesRate: {
+    title: '转化率',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      border: '0.5px solid #ddd',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top"
+        content="广告转化率,系统计算,广告订单量/点击量*100%,展示型推广vCPM成本类型的广告活动不予计算。">
+            <span>
+                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
+            </span>
+            </el-tooltip>
+            <span>转化率</span>
+            </span>
+      )
+      }
+    }
+  },
+  ACOS: {
+    title: 'ACOS',
+    column: {
+      align: 'right',
+      width: '120px',
+      sortable: true,
+      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>ACOS</span>
+            </span>
+      )
+      }
+    }
+  },
+  ROAS: {
+    title: 'ROAS',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '120px',
+      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>ROAS</span>
+            </span>
+      )
+      }
+    }
+  },
+  CPA: {
+    title: '订单成本',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '130px',
+      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>
+      )
+      }
+    }
+  },
+  ViewableImpression: {
+    title: '可见曝光量',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '140px',
+      border: '0.5px solid #ddd',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top"
+        content="来自亚马逊广告API,符合媒体评级委员会(Media Ratings Council)可见标准的曝光量">
+            <span>
+                <el-icon size="14" style="display:inline-block; padding-top:2px; margin-right:3px;"><InfoFilled/></el-icon>
+            </span>
+            </el-tooltip>
+            <span>可见曝光量</span>
+            </span>
+      )
+      }
+    }
+  },
+  DPV: {
+    title: '商品详情页浏览次数',
+    column: {
+      align: 'right',
+      sortable: true,
+      width: '200px',
+      border: '0.5px solid #ddd',
+      renderHeader() {
+        return (
+            <span>
+                <el-tooltip placement="top" content="商品详情页浏览次数,来自亚马逊广告API。<br />
+        在点击广告后的14天内(品牌推广)、浏览或点击广告后的14天内(展示型推广)的商品详情页浏览次数" raw-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>
+      )
+      }
+    }
+  }
+}
+
+
+export function createMul(mul) {
+  return function(number) {
+    return number * mul
+  }
+}

+ 187 - 0
src/views/adManage/utils/enum.ts

@@ -113,3 +113,190 @@ export const TargetExpressionEnum = [
   // { label: '', value: 'ASIN_EXPANDED_FROM' },
   { label: '其它', value: 'OTHER' },
 ]
+
+export 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: '搜索结果顶部展示份额'
+  },
+]
+
+export 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: '搜索结果顶部展示份额'
+  },
+]
+
+export const pieOptions = [
+  {
+    value: 'Spend',
+    label: '花费',
+  },
+  {
+    value: 'TotalSales',
+    label: '销售额',
+  },
+  {
+    value: 'TotalPurchases',
+    label: '订单数',
+  },
+  {
+    value: 'TotalUnitOrdered',
+    label: '销量',
+  },
+  {
+    value: 'Impression',
+    label: '曝光量',
+  },
+  {
+    value: 'Click',
+    label: '点击量',
+  },
+]