adActivityDialog.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. <script lang="ts" setup>
  2. /**
  3. * @Name: adActivityDialog.vue
  4. * @Description:广告关联活动弹窗
  5. * @Author: xinyan
  6. */
  7. import { computed, onMounted, ref, watch } from 'vue';
  8. import { getAdGroupList, getRelationCampaign } from '/@/views/efTools/automation/api';
  9. import { storeToRefs } from 'pinia';
  10. import { useShopInfo } from '/@/stores/shopInfo';
  11. import { ElMessage } from 'element-plus';
  12. const props = defineProps({
  13. templateId: {
  14. type: [String, Number],
  15. required: true,
  16. },
  17. activeModel: {
  18. type: String,
  19. },
  20. modelValue: {
  21. type: Boolean,
  22. required: true,
  23. },
  24. });
  25. const emits = defineEmits(['update:modelValue']);
  26. const shopInfo = useShopInfo();
  27. const { profile } = storeToRefs(shopInfo);
  28. const { templateId } = toRefs(props);
  29. const { activeModel } = toRefs(props);
  30. const dialogVisible = ref(props.modelValue);
  31. //筛选条件
  32. const searchAdCampaign = ref('');
  33. const selectedCampaignType = ref('sp');
  34. const selectedAdGroup = ref('');
  35. const selectedStatus = ref('');
  36. const adCampaignName = ref([]);
  37. const campaignType = [
  38. { value: 'sb', label: 'SB' },
  39. { value: 'sp', label: 'SP' },
  40. { value: 'sd', label: 'SD' },
  41. ];
  42. const adGroups = ref([]);
  43. const campaignStatus = [
  44. { value: 0, label: '未存档' },
  45. { value: 'ENABLED', label: '已启用' },
  46. { value: 'PAUSED', label: '已暂停' },
  47. ];
  48. //表格
  49. const currentPage = ref(1);
  50. const pageSize = ref(25);
  51. const total = ref(0);
  52. const loading = ref(false);
  53. const refTable = ref(null);
  54. let selectedAds = ref([]);
  55. let selections = []; // 添加选中的项
  56. function handleCurrentChange(newPage) {
  57. currentPage.value = newPage;
  58. loading.value = true;
  59. fetchAdCampaign();
  60. }
  61. // 处理分页器每页显示条目数变化
  62. function handleSizeChange(newSize) {
  63. pageSize.value = newSize;
  64. currentPage.value = 1;
  65. fetchAdCampaign();
  66. }
  67. let isRestoringSelection = false;
  68. async function fetchAdCampaign() {
  69. try {
  70. loading.value = true;
  71. const cachedSelectedAds = [...selectedAds.value];
  72. const resp = await getRelationCampaign({
  73. profileId: profile.value.profile_id,
  74. templateId: templateId.value,
  75. campaignName: searchAdCampaign.value,
  76. portfolioId: selectedAdGroup.value,
  77. campaignStatus: selectedStatus.value,
  78. campaignType: selectedCampaignType.value,
  79. page: currentPage.value,
  80. limit: pageSize.value,
  81. });
  82. adCampaignName.value = resp.data;
  83. total.value = resp.total;
  84. currentPage.value = resp.page;
  85. // 开始恢复勾选状态
  86. isRestoringSelection = true;
  87. nextTick(() => {
  88. adCampaignName.value.forEach(item => {
  89. if (cachedSelectedAds.some(ad => ad.campaignId === item.campaignId)) {
  90. refTable.value.toggleRowSelection(item, true);
  91. }
  92. });
  93. isRestoringSelection = false;
  94. });
  95. } catch (error) {
  96. ElMessage.error('请求广告活动数据失败');
  97. } finally {
  98. loading.value = false;
  99. }
  100. }
  101. function handleSelectionChange(selection) {
  102. if (isRestoringSelection) return; // 恢复勾选时跳过该方法
  103. selections = selection;
  104. const newSelections = selections.filter(
  105. (sel) => !selectedAds.value.some((added) => added.campaignId === sel.campaignId)
  106. );
  107. if (newSelections.length > 0) {
  108. selectedAds.value.push(...newSelections);
  109. }
  110. // 处理取消选中的项
  111. const removedSelections = selectedAds.value.filter(
  112. (added) => !selections.some((sel) => sel.campaignId === added.campaignId)
  113. );
  114. if (removedSelections.length > 0) {
  115. selectedAds.value = selectedAds.value.filter(
  116. (added) => !removedSelections.some((removed) => removed.campaignId === added.campaignId)
  117. );
  118. }
  119. }
  120. function removeSelectedAd(index) {
  121. const removedAd = selectedAds.value.splice(index, 1)[0];
  122. const targetIndex = adCampaignName.value.findIndex(ad => ad.campaignName === removedAd.campaignName);
  123. if (targetIndex !== -1) {
  124. const adTable = refTable.value;
  125. adTable.toggleRowSelection(adCampaignName.value[targetIndex], false);
  126. }
  127. }
  128. function removeAllSelectedAds() {
  129. const adTable = refTable.value;
  130. selectedAds.value.forEach(ad => {
  131. const targetIndex = adCampaignName.value.findIndex(item => item.campaignName === ad.campaignName);
  132. if (targetIndex !== -1) {
  133. adTable.toggleRowSelection(adCampaignName.value[targetIndex], false);
  134. }
  135. });
  136. selectedAds.value = [];
  137. }
  138. function cancel() {
  139. dialogVisible.value = false;
  140. }
  141. //TODO: 确认按钮
  142. async function confirm() {
  143. const campaignItems = selectedAds.value.map(ad => ({
  144. campaignId: ad.campaignId,
  145. campaignType: ad.campaignType
  146. }));
  147. const adGroupInfo = [];
  148. const campaignTargetInfo = [
  149. { targetId: '492707808377423', adGroup_id: '448117369011017', bid: 0.45 },
  150. ];
  151. const campaignKeywordInfo = [
  152. { keywordId: '416969576305724', adGroup_id: '393554556566271', bid: 0.04 },
  153. ];
  154. const requestData = {
  155. profileId: profile.value.profile_id,
  156. templateId: templateId.value,
  157. campaignItems,
  158. adGroupInfo,
  159. campaignTargetInfo,
  160. campaignKeywordInfo
  161. };
  162. //console.log('requestData', requestData);
  163. }
  164. // 获取广告组下拉框
  165. async function fetchAdGroupList() {
  166. try {
  167. const resp = await getAdGroupList({
  168. profileId: profile.value.profile_id,
  169. });
  170. adGroups.value = resp.data.map((item: any) => {
  171. return {
  172. label: item.name,
  173. value: item.portfolioId
  174. };
  175. });
  176. } catch (error) {
  177. ElMessage.error('请求失败');
  178. }
  179. }
  180. const headerCellStyle = (args) => {
  181. if (args.rowIndex === 0) {
  182. return {
  183. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  184. fontWeight: '500',
  185. };
  186. }
  187. };
  188. const cellStyle = () => {
  189. return {
  190. fontSize: '13px',
  191. //fontWeight: '500',
  192. };
  193. };
  194. //监控筛选条件变化
  195. watch(selectedCampaignType, () => {
  196. fetchAdCampaign();
  197. });
  198. watch(selectedAdGroup, (newVal) => {
  199. if (newVal) {
  200. selectedAdGroup.value = newVal;
  201. fetchAdCampaign();
  202. }
  203. });
  204. watch(selectedStatus, () => {
  205. fetchAdCampaign();
  206. });
  207. watch(templateId, () => {
  208. fetchAdCampaign();
  209. fetchAdGroupList();
  210. });
  211. watch(() => props.modelValue, (newValue) => {
  212. dialogVisible.value = newValue;
  213. });
  214. watch(dialogVisible, (newValue) => {
  215. emits('update:modelValue', newValue);
  216. });
  217. const treeProps = computed(() => {
  218. if (activeModel.value === 'adGroup' || activeModel.value === 'specified') {
  219. return { children: 'campaignGroupInfo' };
  220. }
  221. return {};
  222. });
  223. onMounted(() => {
  224. //fetchAdGroupList();
  225. });
  226. </script>
  227. <template>
  228. <el-dialog
  229. v-model="dialogVisible"
  230. class="custom-dialog"
  231. style="border-radius: 10px;"
  232. title="关联广告活动"
  233. width="1158px"
  234. >
  235. <div class="container">
  236. <div class="container-left">
  237. <el-input v-model="searchAdCampaign" placeholder="请输入广告活动" style="width: 100%;"
  238. @change="fetchAdCampaign()"></el-input>
  239. <div class="custom-inline">
  240. <el-select v-model="selectedCampaignType" placeholder="选择广告类型">
  241. <el-option v-for="item in campaignType"
  242. :key="item.value"
  243. :label="item.label"
  244. :value="item.value"
  245. ></el-option>
  246. </el-select>
  247. <el-select v-model="selectedAdGroup" clearable placeholder="广告组合" style="margin-bottom: 10px;">
  248. <el-option
  249. v-for="item in adGroups"
  250. :key="item.value"
  251. :label="item.label"
  252. :value="item.value"
  253. ></el-option>
  254. </el-select>
  255. <el-select v-model="selectedStatus" placeholder="状态" style="margin-bottom: 10px;">
  256. <el-option
  257. v-for="item in campaignStatus"
  258. :key="item.value"
  259. :label="item.label"
  260. :value="item.value"
  261. ></el-option>
  262. </el-select>
  263. </div>
  264. <el-table
  265. ref="refTable"
  266. v-loading="loading"
  267. :cell-style="cellStyle"
  268. :data="adCampaignName"
  269. :row-key="'campaignId'"
  270. :header-cell-style="headerCellStyle"
  271. height="400"
  272. style="width: 100%;"
  273. :tree-props="treeProps"
  274. v-bind="activeModel === 'adGroup' || activeModel === 'specified' ? treeProps : {}"
  275. @selection-change="handleSelectionChange"
  276. >
  277. <el-table-column label="广告活动名称">
  278. <template #default="scope">
  279. <el-tag
  280. v-if="scope.row.campaignType"
  281. :color="scope.row.campaignType === 'sb' ? '#0163d2' : (scope.row.campaignType === 'sp' ? '#ff7424' : '#365672')"
  282. class="campaign-type"
  283. disable-transitions
  284. round>
  285. {{ scope.row.campaignType }}
  286. </el-tag>
  287. {{ scope.row.campaignName }}
  288. {{ scope.row.adGroupName }}
  289. </template>
  290. </el-table-column>
  291. <el-table-column type="selection" width="55"></el-table-column>
  292. </el-table>
  293. <div class="pagination-container mt-4">
  294. <el-pagination
  295. v-model:current-page="currentPage"
  296. :page-size="pageSize"
  297. :page-sizes="[10, 25, 50,100,200]"
  298. :total="total"
  299. background
  300. layout="total,sizes,prev, next, jumper"
  301. small
  302. @size-change="handleSizeChange"
  303. @current-change="handleCurrentChange"
  304. />
  305. </div>
  306. </div>
  307. <div class="container-right">
  308. <h3>已选择({{ selectedAds.length }})</h3>
  309. <el-table
  310. :cell-style="cellStyle"
  311. :data="selectedAds"
  312. :header-cell-style="headerCellStyle"
  313. height="484"
  314. :tree-props="treeProps"
  315. v-bind="activeModel === 'adGroup' || activeModel === 'specified' ? treeProps : {}"
  316. style="width: 100%; margin-top: 20px;"
  317. >
  318. <el-table-column label="广告活动" prop="campaignName">
  319. <template #default="scope">
  320. <el-tag
  321. v-if="scope.row.campaignType"
  322. :color="scope.row.campaignType === 'sb' ? '#0163d2' : (scope.row.campaignType === 'sp' ? '#ff7424' : '#365672')"
  323. class="campaign-type"
  324. disable-transitions
  325. round>
  326. {{ scope.row.campaignType }}
  327. </el-tag>
  328. {{ scope.row.campaignName }}
  329. {{ scope.row.adGroupName }}
  330. </template>
  331. </el-table-column>
  332. <el-table-column
  333. align="center"
  334. label="操作"
  335. width="100"
  336. >
  337. <template #header>
  338. <el-button link size="default" style="color: #2077d7" @click="removeAllSelectedAds">删除全部</el-button>
  339. </template>
  340. <template #default="scope">
  341. <el-button type="text" @click="removeSelectedAd(scope.$index)">
  342. <CircleClose style="width:16px;color:#4b5765" />
  343. </el-button>
  344. </template>
  345. </el-table-column>
  346. </el-table>
  347. </div>
  348. </div>
  349. <template #footer>
  350. <div class="dialog-footer">
  351. <el-button @click="cancel">取消</el-button>
  352. <el-button type="primary" @click="confirm">确定</el-button>
  353. </div>
  354. </template>
  355. </el-dialog>
  356. </template>
  357. <style scoped>
  358. .pagination-container {
  359. display: flex;
  360. flex-direction: row-reverse;
  361. }
  362. .custom-inline {
  363. display: flex;
  364. justify-content: space-around;
  365. margin: 12px 0;
  366. gap: 4px;
  367. }
  368. .campaign-type {
  369. width: 35px;
  370. text-align: center;
  371. height: 22px;
  372. font-size: 13px;
  373. font-weight: 400;
  374. color: #fff;
  375. border-color: #fff;
  376. line-height: 22px;
  377. border-radius: 12px;
  378. margin-right: 4px;
  379. }
  380. .container {
  381. width: 100%;
  382. height: 100%;
  383. border: 1px solid #d6dbe2;
  384. border-radius: 4px;
  385. display: flex;
  386. overflow: hidden;
  387. align-content: center;
  388. flex-wrap: nowrap;
  389. flex-direction: row;
  390. /* padding: 10px; */
  391. }
  392. .container-left {
  393. width: 50%;
  394. border-right: 1px solid #d6dbe2;
  395. padding: 15px
  396. }
  397. .container-right {
  398. flex: 1;
  399. padding: 15px
  400. }
  401. </style>