guojing_wu 1 anno fa
parent
commit
bf061ac5b3

+ 2 - 1
.env.development

@@ -2,7 +2,8 @@
 ENV = 'development'
 
 # 本地环境接口地址
-VITE_API_URL = 'http://127.0.0.1:8000'
+# VITE_API_URL = 'http://127.0.0.1:8000'
+VITE_API_URL = 'http://192.168.1.225/'
 
 # 是否启用按钮权限
 VITE_PM_ENABLED = true

+ 7 - 3
src/components/DateRangePicker/index.vue

@@ -12,6 +12,7 @@
       value-format="YYYY-MM-DD"
       :disabled-date="disabledDate"
       :clearable="false"
+      :popper-options="{placement: props.popperPlacement}"
       @change="$emit('change', dateRangeValue)"
     />
   </div>
@@ -20,11 +21,14 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 import dayjs, { Dayjs } from 'dayjs'
-import { start } from 'nprogress';
 
-const props = defineProps<{timezone: string}>()
 
-const dateRangeValue = ref([])
+const props = defineProps({
+  timezone: { type: String, required: true },
+  popperPlacement: { type: String, default: 'bottom-start' }
+})
+
+const dateRangeValue = ref(['2023-10-01', '2023-10-10'])
 
 
 function disabledDate(datetime: Date) {

+ 9 - 0
src/theme/app.scss

@@ -322,3 +322,12 @@ body,
 		padding-left: #{$i}px !important;
 	}
 }
+
+// 自定义全局样式
+.ads-container {
+  padding: 5px;
+}
+
+.fs-page-custom {
+	position: initial !important;
+}

+ 16 - 0
src/theme/element.scss

@@ -319,3 +319,19 @@
 		overflow: auto;
 	}
 }
+
+
+.fs-crud-header {
+	padding: 0 0 5px 0 !important;
+}
+
+.el-tabs__header {
+	margin-bottom: 0px;
+}
+.fs-crud-search {
+	height: auto;
+	padding: auto;
+}
+.fs-search-box {
+	height: 45px;
+}

+ 80 - 26
src/views/adManage/portfolios/crud.tsx

@@ -24,6 +24,12 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 
 	return {
 		crudOptions: {
+			table: {
+				height: 800,
+			},
+			container: {
+        fixedHeight: false
+      },
 			request: {
 				pageRequest,
 				addRequest,
@@ -70,28 +76,39 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 				},
 			},
 			columns: {
-				_index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						//type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-						formatter: (context) => {
-							//计算序号,你可以自定义计算规则,此处为翻页累加
-							let index = context.index ?? 1;
-							let pagination = crudExpose!.crudBinding.value.pagination;
-							// @ts-ignore
-							return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
-						},
-					},
-				},
+				// _index: {
+				// 	title: '序号',
+				// 	form: { show: false },
+				// 	column: {
+				// 		//type: 'index',
+				// 		align: 'center',
+				// 		width: '70px',
+				// 		columnSetDisabled: true, //禁止在列设置中选择
+				// 		formatter: (context) => {
+				// 			//计算序号,你可以自定义计算规则,此处为翻页累加
+				// 			let index = context.index ?? 1;
+				// 			let pagination = crudExpose!.crudBinding.value.pagination;
+				// 			// @ts-ignore
+				// 			return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
+				// 		},
+				// 	},
+				// },
         name: {
           title: '广告组合',
           column: {
             width: '150px'
-          }
+          },
+					search: {
+						show: true,
+						component: {
+							props: {
+								clearable: true
+							}
+						}
+					},
+					form: {
+						rules: [{required: true, message:'必填项'}]
+					}
         },
         state: {
           title: '状态',
@@ -101,22 +118,59 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
               {value:'enabled', label:'投放中'},
               {value:'disable', label:'禁用'},
             ] 
-          })
+          }),
+					form: {
+						show: false
+					}
         },
-        // servingStatus: {
-        //   title: ''
-        // },
+				budget_policy: {
+					title: '预算类型',
+					type: 'dict-select',
+					dict: dict({
+						data: [
+							{ value: '', label: '无预算上限' },
+							{ value: 'dateRange', label: '日期范围' },
+							{ value: 'monthlyRecurring', label: '按月' },
+						]
+					}),
+					form: {
+						value: ''
+					}
+				},
         budget_startDate: {
-          title: '开始日期'
+          title: '开始日期',
+					type: 'date',
+					form: {
+						show: compute(context => context.form.budget_policy === "dateRange"),
+						rules: [{required: true, message:'必填项'}]
+					}
         },
         budget_endDate: {
-          title: '结束日期'
+          title: '结束日期',
+					type: 'date',
+					form: {
+						show: compute(context => context.form.budget_policy !== '')
+					}
         },
         budget_amount: {
-          title: '预算'
+          title: '预算',
+					type: 'number',
+					form: {
+						value: 0,
+						show: compute(context => context.form.budget_policy !== ''),
+						rules: [{required: true, message:'必填项'}],
+						component: {
+							min: 0,
+							precision: 2,
+							controlsPosition: "right"
+						}
+					}
         },
         inBudget: {
-          title: '是否预算内'
+          title: '是否预算内',
+					form: {
+						show: false
+					}
         },
         ...BaseColumn
 			},

+ 30 - 5
src/views/adManage/portfolios/index.vue

@@ -1,14 +1,23 @@
 <template>
-	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
-	</fs-page>
+	<div class="ads-container">
+		<div class="public-search">
+			<DateRangePicker timezone="America/Los_Angeles"></DateRangePicker>
+		</div>
+		<fs-page class="fs-page-custom">
+			<fs-crud ref="crudRef" v-bind="crudBinding">
+				<template #header-middle>
+					<el-card style="height: 500px;">此处用于显示可视化图形</el-card>
+				</template>
+			</fs-crud>
+		</fs-page>
+	</div>
 </template>
 
 <script lang="ts" setup name="Portfolios">
 import { ref, onMounted, defineAsyncComponent } from 'vue';
 import { useFs } from '@fast-crud/fast-crud';
 import { createCrudOptions } from './crud';
-
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
 
 const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
 
@@ -16,4 +25,20 @@ const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context:
 onMounted(() => {
 	crudExpose.doRefresh();
 });
-</script>
+</script>
+
+<style>
+
+.public-search {
+  display: flex;
+  gap: 3px;
+  padding-bottom: 3px;
+  position: sticky;
+  top: 0;
+  z-index: 2;
+  width: 100%;
+  background-color: #f8f8f8;
+  box-shadow: 0px 0px 0px rgba(51,89,181,0.16);
+}
+
+</style>

+ 9 - 0
src/views/adManage/sp/campaigns/CreateCampaigns/index.vue

@@ -0,0 +1,9 @@
+<template>
+  <p>新建广告页面</p>
+</template>
+
+<script lang="ts" setup>
+</script>
+
+<style scoped>
+</style>

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

@@ -0,0 +1,42 @@
+import { request } from '/@/utils/service';
+import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+import XEUtils from 'xe-utils';
+
+export const apiPrefix = '/api/ad_manage/spCampaigns/';
+export function GetList(query: PageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: InfoReq) {
+    return request({
+        url: apiPrefix + id,
+        method: 'get',
+    });
+}
+
+export function AddObj(obj: AddReq) {
+    return request({
+        url: apiPrefix,
+        method: 'post',
+        data: obj,
+    });
+}
+
+export function UpdateObj(obj: EditReq) {
+    return request({
+        url: apiPrefix + obj.id + '/',
+        method: 'put',
+        data: obj,
+    });
+}
+
+export function DelObj(id: DelReq) {
+    return request({
+        url: apiPrefix + id + '/',
+        method: 'delete',
+        data: { id },
+    });
+}

+ 138 - 0
src/views/adManage/sp/campaigns/crud.tsx

@@ -0,0 +1,138 @@
+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: 800
+			},
+			container: {
+        fixedHeight: false
+      },
+			actionbar: {
+				show: true,
+				buttons: {
+					add: {
+						show: false
+					},
+					create: {
+						text: '新建广告活动',
+						type: "primary",
+						show: true,
+						click() {
+
+						}
+					}
+				}
+			},
+			search: {
+				show: false
+			},
+			toolbar: {
+        buttons: {
+					search: {
+						show: true
+					},
+					compact: {
+						show: false
+					}
+				}
+			},
+			request: {
+				pageRequest,
+				addRequest,
+				editRequest,
+				delRequest,
+			},
+			rowHandle: {
+				fixed: 'right',
+				width: 80,
+				buttons: {
+					view: {
+						show: false,
+					},
+					edit: {
+						iconRight: 'Edit',
+						type: 'text',
+            text: null
+						// show: hasPermissions('dictionary:Update'),
+					},
+					remove: {
+						iconRight: 'Delete',
+						type: 'text',
+            text: null
+						// show: hasPermissions('dictionary:Delete'),
+					},
+				},
+			},
+			columns: {
+        name: {
+          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: '状态'
+				},
+				startDate: {
+					title: '开始日期'
+				},
+				endDate: {
+					title: '结束日期'
+				},
+				budget: {
+					title: '预算'
+				},
+				portfolio: {
+					title: '广告组合'
+				},
+        ...BaseColumn
+			}
+		}
+	}
+}

+ 24 - 0
src/views/adManage/sp/campaigns/index.vue

@@ -0,0 +1,24 @@
+<template>
+	<fs-page class="fs-page-custom">
+    <fs-crud ref="crudRef" v-bind="crudBinding">
+			<template #header-middle>
+				<el-card style="height: 500px;margin-bottom: 5px;" shadow="hover">图形</el-card>
+			</template>
+		</fs-crud>
+	</fs-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, defineAsyncComponent } from 'vue';
+import { useFs, FsPage } from '@fast-crud/fast-crud';
+import { createCrudOptions } from './crud';
+
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
+
+onMounted(() => {
+	crudExpose.doRefresh();
+});
+</script>
+
+<style scoped>
+</style>

+ 41 - 23
src/views/adManage/sp/index.vue

@@ -1,36 +1,54 @@
 <template>
-  <div>
-    <el-menu
-      :default-active="activeIndex"
-      :class="className"
-      mode="horizontal"
-      background-color="#FFFFFF"
-      @select="handleSelect"
-    >
-      <el-menu-item index="1">广告活动</el-menu-item>
-      <el-menu-item index="2">关键词投放</el-menu-item>
-      <el-menu-item index="3">商品投放</el-menu-item>
-      <el-menu-item index="4">搜索词</el-menu-item>
-      <el-menu-item index="5">广告位</el-menu-item>
-    </el-menu>
+  <div class="ads-container">
+    <div class="public-search">
+      <DateRangePicker timezone="America/Los_Angeles" @change="changeDateRange"></DateRangePicker>
+      <el-select v-model="portfolios" placeholder="广告组合"></el-select>
+    </div>
+    <el-tabs>
+      <el-tab-pane label="广告活动">
+        <campaigns></campaigns>
+      </el-tab-pane>
+      <el-tab-pane label="关键词投放"></el-tab-pane>
+      <el-tab-pane label="商品投放"></el-tab-pane>
+      <el-tab-pane label="搜索词"></el-tab-pane>
+      <el-tab-pane label="广告位"></el-tab-pane>
+    </el-tabs>
   </div>
 </template>
 
 <script lang="ts" setup>
-import {ref, onMounted, onUnmounted} from 'vue'
+import DateRangePicker from '/@/components/DateRangePicker/index.vue'
+import campaigns from './campaigns/index.vue'
+import { ref } from 'vue'
 
+const portfolios = ref([])
 
-const className = ref('box-fixed')
-const activeIndex = ref('1')
-
-const handleSelect = (key: string, keyPath: string[]) => {
-  console.log(key, keyPath)
+function changeDateRange(val: string[]) {
+  console.log(val)
 }
+
 </script>
 
 <style scoped>
-.box-fixed {
-  position: fixed;
-  top: 30;
+.public-search {
+  display: flex;
+  gap: 3px;
+  padding-bottom: 3px;
+  position: sticky;
+  top: 0;
+  z-index: 2;
+  width: 100%;
+  background-color: #f8f8f8;
+  box-shadow: 0px 0px 0px rgba(51,89,181,0.16);
+}
+:deep(.el-tabs__header.is-top) {
+  background-color: #fff;
+  position: sticky;
+  top: 32px;
+  z-index: 1;
+  box-shadow: 0px 0px 12px rgba(51,89,181,0.16);
+}
+:deep(.el-tabs__nav) {
+  padding-left: 10px;
 }
 </style>

+ 44 - 11
src/views/adManage/utils/commonTabColumn.js

@@ -1,36 +1,69 @@
 export const BaseColumn = {
   impressions: {
-    title: '曝光量'
+    title: '曝光量',
+    form: {
+      show: false
+    }
   },
   clicks: {
-    title: '点击量'
+    title: '点击量',
+    form: {
+      show: false
+    }
   },
   ctr: {
-    title: '点击率'
+    title: '点击率',
+    form: {
+      show: false
+    }
   },
   spend: {
-    title: '花费'
+    title: '花费',
+    form: {
+      show: false
+    }
   },
   cpc: {
-    title: '点击成本'
+    title: '点击成本',
+    form: {
+      show: false
+    }
   },
   purchases: {
-    title: '订单量'
+    title: '订单量',
+    form: {
+      show: false
+    }
   },
   unitsOrdered: {
-    title: '销量'
+    title: '销量',
+    form: {
+      show: false
+    }
   },
   sales: {
-    title: '销售额'
+    title: '销售额',
+    form: {
+      show: false
+    }
   },
   acos: {
-    title: 'ACOS'
+    title: 'ACOS',
+    form: {
+      show: false
+    }
   },
   roas: {
-    title: 'ROAS'
+    title: 'ROAS',
+    form: {
+      show: false
+    }
   },
   cpa: {
-    title: '订单成本'
+    title: '订单成本',
+    form: {
+      show: false
+    }
   }
 }
 

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

@@ -0,0 +1,42 @@
+import { request } from '/@/utils/service';
+import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
+import XEUtils from 'xe-utils';
+
+export const apiPrefix = '/api/ad_manage/';
+export function GetList(query: PageQuery) {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+        params: query,
+    })
+}
+export function GetObj(id: InfoReq) {
+    return request({
+        url: apiPrefix + id,
+        method: 'get',
+    });
+}
+
+export function AddObj(obj: AddReq) {
+    return request({
+        url: apiPrefix,
+        method: 'post',
+        data: obj,
+    });
+}
+
+export function UpdateObj(obj: EditReq) {
+    return request({
+        url: apiPrefix + obj.id + '/',
+        method: 'put',
+        data: obj,
+    });
+}
+
+export function DelObj(id: DelReq) {
+    return request({
+        url: apiPrefix + id + '/',
+        method: 'delete',
+        data: { id },
+    });
+}

+ 117 - 0
src/views/demo/crud.tsx

@@ -0,0 +1,117 @@
+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 { successMessage } from '/@/utils/message';
+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: 800,
+				size: "small",
+			},
+			container: {
+        fixedHeight: 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'),
+					},
+					// custom: {
+					// 	text: '字典配置',
+					// 	type: 'text',
+					// 	// show: hasPermissions('dictionary:Update'),
+					// 	tooltip: {
+					// 		placement: 'top',
+					// 		content: '字典配置',
+					// 	},
+					// 	//@ts-ignore
+					// 	click: (ctx: any) => {
+					// 		const { row } = ctx;
+					// 		context!.subDictRef.value.drawer = true;
+					// 		nextTick(() => {
+					// 			context!.subDictRef.value.setSearchFormData({ form: { parent: row.id } });
+					// 			context!.subDictRef.value.doRefresh();
+					// 		});
+					// 	},
+					// },
+				},
+			},
+			columns: {
+				// _index: {
+				// 	title: '序号',
+				// 	form: { show: false },
+				// 	column: {
+				// 		//type: 'index',
+				// 		align: 'center',
+				// 		width: '70px',
+				// 		columnSetDisabled: true, //禁止在列设置中选择
+				// 		formatter: (context) => {
+				// 			//计算序号,你可以自定义计算规则,此处为翻页累加
+				// 			let index = context.index ?? 1;
+				// 			let pagination = crudExpose!.crudBinding.value.pagination;
+				// 			// @ts-ignore
+				// 			return ((pagination.currentPage ?? 1) - 1) * pagination.pageSize + index + 1;
+				// 		},
+				// 	},
+				// },
+        name: {
+          title: '广告活动',
+          column: {
+            width: '150px'
+          },
+					search: {
+						show: true,
+						component: {
+							props: {
+								clearable: true
+							}
+						}
+					},
+					form: {
+						rules: [{required: true, message:'必填项'}]
+					}
+        }
+			}
+		}
+	}
+}