adActivityDialog.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <script lang="ts" setup>
  2. /**
  3. * @Name: adActivityDialog.vue
  4. * @Description:广告关联活动弹窗
  5. * @Author: xinyan
  6. */
  7. import { computed, onMounted, reactive, ref, toRefs, watch } from 'vue';
  8. import {
  9. getAdGroupList,
  10. getRelationCampaign,
  11. getSelectedAdCampaign,
  12. updateAdCampaign
  13. } from '/@/views/efTools/automation/api';
  14. import { storeToRefs } from 'pinia';
  15. import { useShopInfo } from '/@/stores/shopInfo';
  16. import { ElMessage } from 'element-plus';
  17. import TargetRuleDialog from '/@/views/efTools/automation/components/targetRuleDialog.vue';
  18. import { DocumentAdd } from '@element-plus/icons-vue';
  19. import { allCampaignTypes, campaignStatus, filterCampaignTypes } from '/@/views/efTools/utils/enum';
  20. const shopInfo = useShopInfo();
  21. const { profile } = storeToRefs(shopInfo);
  22. const props = defineProps({
  23. rowData: {
  24. type: Object,
  25. required: true,
  26. },
  27. modelValue: {
  28. type: Boolean,
  29. required: true,
  30. },
  31. });
  32. const emits = defineEmits(['confirmSuccess']);
  33. const { rowData } = toRefs(props);
  34. const templateId = ref(rowData.value.id);
  35. const activeModel = ref(rowData.value.rule.activeModel);
  36. const campaignType = ref(rowData.value.rule.campaignType);
  37. const dialogVisible = defineModel({ default: false });
  38. // 定向规则
  39. const selected = ref([]); //存储后端中已存在的广告组
  40. const targetRuleDialogVisible = ref(false);
  41. const selectedTargetedRow = ref(null);
  42. let selectedGroups = [];
  43. // 筛选条件
  44. const searchAdCampaign = ref('');
  45. const selectedCampaignType = ref(campaignType.value || ''); // 默认选中当前的 campaignType
  46. const selectedAdGroup = ref('');
  47. const selectedStatus = ref('');
  48. const adGroups = ref([]);
  49. // 表格
  50. const currentPage = ref(1);
  51. const pageSize = ref(25);
  52. const total = ref(0);
  53. const loading = ref(false);
  54. const xGridOne = ref(null);
  55. const xGridTwo = ref(null);
  56. let selectedAds = ref([]);
  57. const filteredCampaignType = computed(() => {
  58. const selectedCampaignType = props.rowData.rule.campaignType;
  59. if (selectedCampaignType) {
  60. return allCampaignTypes.filter(item => item.value === selectedCampaignType);
  61. }
  62. else if (props.rowData.rule.type === 2 || props.rowData.rule.type === 5 || props.rowData.rule.type === 6){
  63. return filterCampaignTypes;
  64. }
  65. return allCampaignTypes;
  66. });
  67. const selectedColumns = computed(() => [
  68. {
  69. field: 'campaignName',
  70. title: '广告活动',
  71. slots: { default: 'campaignName_default' },
  72. treeNode: activeModel.value == 'specified' || activeModel.value == 'adGroup'
  73. },
  74. { title: '操作', width: 100, align: 'center', slots: { header: 'header_operation', default: 'default_operation' } }
  75. ]);
  76. const treeProps = computed(() => {
  77. if (activeModel.value === 'adGroup' || activeModel.value === 'specified') {
  78. return {
  79. rowField: 'campaignId',
  80. childrenField: 'campaignGroupInfo', // 子节点字段
  81. expandAll: true,
  82. };
  83. }
  84. return {};
  85. });
  86. const gridOptions = reactive({
  87. border: 'inner',
  88. height: 500,
  89. loading:false,
  90. rowConfig: {
  91. isHover: true,
  92. keyField: 'campaignId',
  93. },
  94. treeConfig: treeProps,
  95. checkboxConfig: {
  96. labelField: 'name',
  97. reserve: true,
  98. checkStrictly: false,
  99. checkMethod: ({ row }) => {
  100. if (activeModel.value === 'specified') {
  101. return row.isSelected;
  102. }
  103. return true;
  104. }
  105. },
  106. columns: computed(() => [
  107. {
  108. field: 'campaignName',
  109. title: '广告活动',
  110. slots: { default: 'campaignName_default' },
  111. treeNode: activeModel.value == 'specified' || activeModel.value == 'adGroup'
  112. },
  113. {
  114. type: 'checkbox',
  115. width: 55,
  116. fixed: 'right',
  117. slots: activeModel.value == 'specified' ? { header: 'checkbox_header', checkbox: 'checkbox_cell' } : ''
  118. },
  119. ]),
  120. data: []
  121. });
  122. function handleCurrentChange(newPage) {
  123. currentPage.value = newPage;
  124. loading.value = true;
  125. fetchAdCampaign();
  126. }
  127. // 处理分页器每页显示条目数变化
  128. function handleSizeChange(newSize) {
  129. pageSize.value = newSize;
  130. currentPage.value = 1;
  131. fetchAdCampaign();
  132. }
  133. function handleAdCampaignChange() {
  134. localStorage.setItem('searchAdCampaign', JSON.stringify(searchAdCampaign.value));
  135. fetchAdCampaign();
  136. }
  137. async function fetchAdCampaign() {
  138. // const savedAdCampaign = localStorage.getItem('searchAdCampaign');
  139. // if (savedAdCampaign) {
  140. // searchAdCampaign.value = JSON.parse(savedAdCampaign);
  141. // }
  142. try {
  143. gridOptions.loading = true;
  144. if (profile.value.profile_id && templateId.value) {
  145. const resp = await getRelationCampaign({
  146. profileId: profile.value.profile_id,
  147. templateId: templateId.value,
  148. campaignName: searchAdCampaign.value,
  149. portfolioId: selectedAdGroup.value,
  150. campaignStatus: selectedStatus.value,
  151. campaignType: selectedCampaignType.value,
  152. page: currentPage.value,
  153. limit: pageSize.value,
  154. });
  155. gridOptions.data = resp.data;
  156. total.value = resp.total;
  157. currentPage.value = resp.page;
  158. }
  159. } catch (error) {
  160. ElMessage.error('请求广告活动数据失败');
  161. } finally {
  162. gridOptions.loading = false;
  163. }
  164. }
  165. function handleGridChange({ records, row, checked }) {
  166. if (activeModel.value === 'specified') {
  167. if (row) {
  168. if (!checked) {
  169. row.isSelected = false;
  170. if (row.campaignGroupInfo) {
  171. // 如果是父节点,清空所有子节点的 keywordInfo 和 campaignTargetInfo
  172. row.campaignGroupInfo.forEach(group => {
  173. group.keywordInfo = [];
  174. group.campaignTargetInfo = [];
  175. });
  176. } else if (row.keywordInfo || row.campaignTargetInfo) {
  177. row.keywordInfo = [];
  178. row.campaignTargetInfo = [];
  179. }
  180. updateSelectedAds();
  181. }
  182. } else {
  183. // 全选/取消全选
  184. records.forEach(record => {
  185. if (record.isSelected) {
  186. record.isSelected = checked;
  187. }
  188. });
  189. updateSelectedAds();
  190. }
  191. } else if (activeModel.value === 'adGroup') {
  192. handleSelectionChange({ records });
  193. } else {
  194. handelSelect({ records });
  195. }
  196. }
  197. function toggleCheckboxEvent(row) {
  198. if (activeModel.value === 'specified') {
  199. if (row.isSelected) {
  200. xGridOne.value.setCheckboxRow(row, false);
  201. handleGridChange({ records: [row], row, checked: false });
  202. }
  203. } else {
  204. // 其他模式下正常切换复选框状态
  205. xGridOne.value.toggleCheckboxRow(row);
  206. }
  207. }
  208. // 非树形结构的表格选择变化
  209. function handelSelect({ records }) {
  210. selectedAds.value = [
  211. ...selectedAds.value.filter(ad => ad.page !== currentPage.value),
  212. ...records.map(ad => ({ ...ad, page: currentPage.value }))
  213. ];
  214. }
  215. function updateSelectedAds() {
  216. const filteredAds = gridOptions.data
  217. .filter(ad => ad.campaignGroupInfo && ad.campaignGroupInfo.some(group => group.isSelected))
  218. .map(ad => ({
  219. ...ad,
  220. campaignGroupInfo: ad.campaignGroupInfo.filter(group => group.isSelected),
  221. page: currentPage.value
  222. }));
  223. selectedAds.value = [
  224. ...selected.value, // 保留已存在的选中广告
  225. ...filteredAds
  226. ];
  227. }
  228. // 树形结构的表格选择变化
  229. function handleSelectionChange({ records }) {
  230. selectedGroups = selectedGroups.filter(group => {
  231. return records.some(record => record.adGroupId === group.adGroupId);
  232. });
  233. let updatedRecords = [];
  234. const parentCampaignMap = new Map();
  235. records.forEach(record => {
  236. if (record.adGroupId) {
  237. // 子节点
  238. const parentCampaign = gridOptions.data.find(campaign =>
  239. campaign.campaignGroupInfo.some(group => group.adGroupId === record.adGroupId)
  240. );
  241. if (parentCampaign) {
  242. // 如果父节点已经存在,则将子节点添加到父节点的 campaignGroupInfo 中
  243. if (parentCampaignMap.has(parentCampaign.campaignId)) {
  244. parentCampaignMap.get(parentCampaign.campaignId).campaignGroupInfo.push(record);
  245. } else {
  246. // 如果父节点不存在,则创建一个新的父节点对象
  247. parentCampaignMap.set(parentCampaign.campaignId, {
  248. campaignType: parentCampaign.campaignType,
  249. campaignId: parentCampaign.campaignId,
  250. campaignName: parentCampaign.campaignName,
  251. campaignGroupInfo: [record]
  252. });
  253. }
  254. }
  255. }
  256. });
  257. // 将所有父节点对象添加到 updatedRecords
  258. updatedRecords = Array.from(parentCampaignMap.values());
  259. // 更新选中的广告
  260. selectedAds.value = [
  261. ...selectedAds.value.filter(ad => ad.page !== currentPage.value),
  262. ...updatedRecords.map(ad => ({ ...ad, page: currentPage.value })),
  263. ];
  264. }
  265. // 选择定向按钮
  266. function handleSelectTarget(row) {
  267. let parent = null;
  268. if (row.keywordInfo && row.keywordInfo.length > 0 || row.campaignTargetInfo && row.campaignTargetInfo.length > 0) {
  269. parent = selectedAds.value.find(campaign =>
  270. campaign.campaignGroupInfo.some(group => group.adGroupId === row.adGroupId)
  271. );
  272. } else {
  273. parent = gridOptions.data.find(campaign =>
  274. campaign.campaignGroupInfo.some(group => group.adGroupId === row.adGroupId)
  275. );
  276. }
  277. selectedTargetedRow.value = {
  278. campaignType: parent.campaignType,
  279. campaignId: parent.campaignId,
  280. adGroupId: row.adGroupId,
  281. isSelected: row.isSelected || false,
  282. keywordInfo: row.keywordInfo || [],
  283. campaignTargetInfo: row.campaignTargetInfo || [],
  284. };
  285. targetRuleDialogVisible.value = true;
  286. }
  287. // 选择定向弹窗确认按钮
  288. function handleConfirm({ campaignInfo, targetType }) {
  289. if (!selectedTargetedRow.value) return;
  290. let parentCampaign = gridOptions.data.find(campaign =>
  291. campaign.campaignGroupInfo.some(group => group.adGroupId === selectedTargetedRow.value.adGroupId)
  292. );
  293. if (!parentCampaign) {
  294. parentCampaign = selectedAds.value.find(campaign =>
  295. campaign.campaignGroupInfo.some(group => group.adGroupId === selectedTargetedRow.value.adGroupId)
  296. );
  297. }
  298. if (parentCampaign) {
  299. // 更新子节点(广告组)的信息
  300. const group = parentCampaign.campaignGroupInfo.find(group => group.adGroupId === selectedTargetedRow.value.adGroupId);
  301. if (group) {
  302. if (targetType === 'keyword') {
  303. group.keywordInfo = campaignInfo;
  304. } else if (targetType === 'target') {
  305. group.campaignTargetInfo = campaignInfo;
  306. }
  307. group.isSelected = true;
  308. group.targetLength = (group.keywordInfo?.length || 0) + (group.campaignTargetInfo?.length || 0);
  309. }
  310. // 勾选表格1中的对应行,只有在定向大于0时进行勾选
  311. if (group && group.targetLength > 0) {
  312. if (xGridOne.value) {
  313. xGridOne.value.toggleCheckboxRow(group, true); // 手动勾选复选框
  314. }
  315. }
  316. }
  317. // 更新选中的广告
  318. updateSelectedAds();
  319. }
  320. // 删除选中的广告
  321. const removeSelectedAd = async (row) => {
  322. if (activeModel.value === 'specified'){
  323. await removeSpecificAd(row)
  324. }else {
  325. await removedAd(row)
  326. }
  327. };
  328. async function removeSpecificAd(row) {
  329. const $grid = xGridTwo.value;
  330. if ($grid) {
  331. if (row.adGroupId) {
  332. // 删除子节点(广告组)
  333. selectedAds.value = selectedAds.value.map(ad => {
  334. if (ad.campaignGroupInfo) {
  335. return {
  336. ...ad,
  337. campaignGroupInfo: ad.campaignGroupInfo.filter(group => group.adGroupId !== row.adGroupId)
  338. };
  339. }
  340. return ad;
  341. }).filter(ad => ad.campaignGroupInfo && ad.campaignGroupInfo.length > 0);
  342. // 更新 selected.value
  343. selected.value = selected.value.map(ad => {
  344. if (ad.campaignGroupInfo) {
  345. const filteredGroupInfo = ad.campaignGroupInfo.filter(group => group.adGroupId !== row.adGroupId);
  346. return {
  347. ...ad,
  348. campaignGroupInfo: filteredGroupInfo // 更新子节点
  349. };
  350. }
  351. return ad; // 返回未修改的广告
  352. }).filter(ad => ad.campaignGroupInfo.length > 0); // 删除无子节点的父节点
  353. } else {
  354. // 删除父节点(广告活动)
  355. selectedAds.value = selectedAds.value.filter(ad => ad.campaignId !== row.campaignId);
  356. selected.value = selected.value.filter(ad => ad.campaignId !== row.campaignId);
  357. }
  358. await $grid.remove(row);
  359. }
  360. if (xGridOne.value) {
  361. // 始终取消选中当前行
  362. await xGridOne.value.setCheckboxRow(row, false);
  363. handleGridChange({ records: [row], row, checked: false });
  364. if (!row.isSelected) {
  365. // 如果当前行不是选中的状态,查找并取消选中对应的子节点
  366. const parentRow = xGridOne.value.data.find(ad => ad.campaignId === row.campaignId);
  367. if (parentRow && parentRow.campaignGroupInfo) {
  368. parentRow.campaignGroupInfo.forEach(group => {
  369. group.isSelected = false;
  370. });
  371. }
  372. }
  373. }
  374. }
  375. const removedAd = async (row) => {
  376. const $grid = xGridTwo.value;
  377. if ($grid) {
  378. if (row.adGroupId) {
  379. // 删除子节点(广告组)
  380. selectedAds.value = selectedAds.value.map(ad => {
  381. if (ad.campaignGroupInfo) {
  382. return {
  383. ...ad,
  384. campaignGroupInfo: ad.campaignGroupInfo.filter(group => group.adGroupId !== row.adGroupId)
  385. };
  386. }
  387. return ad;
  388. }).filter(ad => ad.campaignGroupInfo && ad.campaignGroupInfo.length > 0);
  389. } else {
  390. // 删除父节点(广告活动)
  391. selectedAds.value = selectedAds.value.filter(ad => ad.campaignId !== row.campaignId);
  392. }
  393. await $grid.remove(row);
  394. }
  395. if (xGridOne.value) {
  396. await xGridOne.value.toggleCheckboxRow(row);
  397. row.isSelected = false;
  398. }
  399. };
  400. function removeAllSelectedAds() {
  401. selectedAds.value = [];
  402. const $grid = xGridOne.value;
  403. if ($grid) {
  404. $grid.clearCheckboxRow();
  405. gridOptions.data.forEach(ad => {
  406. if (ad.campaignGroupInfo) {
  407. ad.campaignGroupInfo.forEach(group => {
  408. group.isSelected = false; // 设置子节点的 isSelected 为 false
  409. group.keywordInfo = []; // 清空每个子节点的关键词信息
  410. group.campaignTargetInfo = []; // 清空每个子节点的定向目标信息
  411. });
  412. }
  413. });
  414. }
  415. }
  416. function cancel() {
  417. dialogVisible.value = false;
  418. }
  419. async function confirm() {
  420. const campaignItems = selectedAds.value.map(ad => ({
  421. campaignId: ad.campaignId,
  422. campaignType: ad.campaignType
  423. }));
  424. let campaignKeywordInfo = [];
  425. let campaignTargetInfo = [];
  426. let adGroupInfo = [];
  427. selectedAds.value.forEach(campaign => {
  428. campaign.campaignGroupInfo.forEach(group => {
  429. if (group.keywordInfo && group.keywordInfo.length > 0) {
  430. campaignKeywordInfo = campaignKeywordInfo.concat(
  431. group.keywordInfo.map(keyword => ({
  432. keywordId: keyword.keywordId,
  433. adGroup_id: group.adGroupId,
  434. bid: keyword.bid
  435. }))
  436. );
  437. }
  438. if (group.campaignTargetInfo && group.campaignTargetInfo.length > 0) {
  439. campaignTargetInfo = campaignTargetInfo.concat(
  440. group.campaignTargetInfo.map(target => ({
  441. targetId: target.targetId,
  442. adGroup_id: group.adGroupId,
  443. bid: target.bid
  444. }))
  445. );
  446. }
  447. if (activeModel.value === 'adGroup') {
  448. if (!adGroupInfo.includes(group.adGroupId)) {
  449. adGroupInfo.push(group.adGroupId); // 直接推送 adGroupId
  450. }
  451. }
  452. });
  453. });
  454. const requestData = {
  455. profileId: profile.value.profile_id,
  456. templateId: templateId.value,
  457. campaignItems: campaignItems,
  458. adGroupInfo: adGroupInfo,
  459. campaignTargetInfo,
  460. campaignKeywordInfo
  461. };
  462. try {
  463. const response = await updateAdCampaign(requestData);
  464. if (response.code === 2000) {
  465. dialogVisible.value = false;
  466. emits('confirmSuccess');
  467. ElMessage({ message: '创建成功', type: 'success', });
  468. }
  469. } catch (error) {
  470. console.error('API error:', error);
  471. ElMessage({ message: '创建失败', type: 'error', });
  472. }
  473. }
  474. // 获取已添加定向的广告
  475. async function getSelectedAds() {
  476. const resp = await getSelectedAdCampaign({
  477. templateId: templateId.value,
  478. });
  479. selected.value = resp.data;
  480. // 处理已选择的广告
  481. if (activeModel.value === 'specified') {
  482. selected.value.forEach(ad => {
  483. if (ad.campaignGroupInfo && ad.campaignGroupInfo.length > 0) {
  484. ad.campaignGroupInfo.forEach(group => {
  485. group.isSelected = true;
  486. group.targetLength = group.keywordInfo?.length + (group.campaignTargetInfo?.length || 0);
  487. });
  488. }
  489. });
  490. }
  491. selectedAds.value.push(...selected.value);
  492. }
  493. // 获取广告组下拉框
  494. async function fetchAdGroupList() {
  495. try {
  496. const resp = await getAdGroupList({
  497. profileId: profile.value.profile_id,
  498. });
  499. adGroups.value = resp.data.map((item: any) => {
  500. return {
  501. label: item.name,
  502. value: item.portfolioId
  503. };
  504. });
  505. } catch (error) {
  506. ElMessage.error('请求失败');
  507. }
  508. }
  509. const headerCellStyle = (args) => {
  510. if (args.rowIndex === 0) {
  511. return {
  512. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  513. fontWeight: '500',
  514. };
  515. }
  516. };
  517. const cellStyle = () => {
  518. return {
  519. fontSize: '13px',
  520. //fontWeight: '500',
  521. };
  522. };
  523. //监控筛选条件变化
  524. watch(selectedCampaignType, () => {
  525. fetchAdCampaign();
  526. });
  527. watch(selectedAdGroup, (newVal) => {
  528. if (newVal) {
  529. selectedAdGroup.value = newVal;
  530. fetchAdCampaign();
  531. }
  532. });
  533. watch(selectedStatus, () => {
  534. fetchAdCampaign();
  535. });
  536. // watch(templateId, () => {
  537. // fetchAdCampaign();
  538. // fetchAdGroupList();
  539. // });
  540. // watch(() => props.modelValue, (newValue) => {
  541. // dialogVisible.value = newValue;
  542. // });
  543. // watch(dialogVisible, (newValue) => {
  544. // // emits('update:modelValue', newValue);
  545. // });
  546. onMounted(() => {
  547. fetchAdGroupList();
  548. fetchAdCampaign();
  549. getSelectedAds();
  550. });
  551. </script>
  552. <template>
  553. <el-dialog
  554. v-model="dialogVisible"
  555. style="border-radius: 10px;"
  556. title="关联广告活动"
  557. width="1158px"
  558. >
  559. <div class="container">
  560. <div class="container-left">
  561. <el-input v-model="searchAdCampaign" clearable placeholder="请输入广告活动" style="width: 100%;"
  562. @change="handleAdCampaignChange()"></el-input>
  563. <div class="custom-inline">
  564. <el-select v-model="selectedCampaignType" placeholder="选择广告类型">
  565. <el-option v-for="item in filteredCampaignType"
  566. :key="item.value"
  567. :label="item.label"
  568. :value="item.value"
  569. ></el-option>
  570. </el-select>
  571. <el-select v-model="selectedAdGroup" clearable placeholder="广告组合" style="margin-bottom: 10px;">
  572. <el-option
  573. v-for="item in adGroups"
  574. :key="item.value"
  575. :label="item.label"
  576. :value="item.value"
  577. ></el-option>
  578. </el-select>
  579. <el-select v-model="selectedStatus" placeholder="状态" style="margin-bottom: 10px;">
  580. <el-option
  581. v-for="item in campaignStatus"
  582. :key="item.value"
  583. :label="item.label"
  584. :value="item.value"
  585. ></el-option>
  586. </el-select>
  587. </div>
  588. <vxe-grid ref="xGridOne" :cell-style="cellStyle" :header-cell-style="headerCellStyle" v-bind="gridOptions"
  589. @checkbox-change="handleGridChange" @checkbox-all="handleGridChange">
  590. <template #campaignName_default="{ row }">
  591. <el-tag
  592. v-if="row.campaignType"
  593. :color="row.campaignType === 'sb' ? '#0163d2' : (row.campaignType === 'sp' ? '#ff7424' : '#365672')"
  594. class="campaign-type"
  595. disable-transitions
  596. round>
  597. {{ row.campaignType }}
  598. </el-tag>
  599. <span> {{ row.campaignName }}</span>
  600. <span class="flex-container">
  601. {{ row.adGroupName }}
  602. <el-button
  603. v-if="row.adGroupName && activeModel==='specified'&&!row.isSelected"
  604. class="btn-link"
  605. link
  606. @click="handleSelectTarget(row)">
  607. 选择定向
  608. </el-button>
  609. <span v-else-if="row.adGroupName && activeModel==='specified'">已选择定向</span>
  610. </span>
  611. </template>
  612. <template #checkbox_header="{ checked, indeterminate }">
  613. <span class="custom-checkbox" @click.stop="toggleAllCheckboxEvent">
  614. <i v-if="indeterminate" class="vxe-icon-square-minus-fill" style="color: #0d84ff"></i>
  615. <i v-else-if="checked" class="vxe-icon-square-checked-fill" style="color: #0d84ff"></i>
  616. <i v-else class="vxe-icon-checkbox-unchecked" style="color: #0d84ff"></i>
  617. </span>
  618. </template>
  619. <template #checkbox_cell="{ row, checked, indeterminate }">
  620. <span class="custom-checkbox" @click.stop="toggleCheckboxEvent(row)">
  621. <i v-if="indeterminate" class="vxe-icon-square-minus-fill" style="color: #0d84ff"></i>
  622. <i v-else-if="checked" class="vxe-icon-square-checked-fill" style="color: #0d84ff"></i>
  623. <el-tooltip v-else
  624. class="box-item"
  625. content="请选择定向"
  626. effect="dark"
  627. placement="top"
  628. >
  629. <i class="vxe-icon-checkbox-unchecked" style="color: #0d84ff"></i>
  630. </el-tooltip>
  631. </span>
  632. </template>
  633. </vxe-grid>
  634. <div class="pagination-container mt-4">
  635. <el-pagination
  636. v-model:current-page="currentPage"
  637. :page-size="pageSize"
  638. :page-sizes="[10, 25, 50,100,200]"
  639. :total="total"
  640. background
  641. layout="total,sizes,prev, next, jumper"
  642. small
  643. @size-change="handleSizeChange"
  644. @current-change="handleCurrentChange"
  645. />
  646. </div>
  647. </div>
  648. <div class="container-right">
  649. <h3>已选择({{ selectedAds.length }})</h3>
  650. <vxe-grid ref="xGridTwo"
  651. :cell-style="cellStyle"
  652. :columns="selectedColumns"
  653. :data="selectedAds"
  654. :header-cell-style="headerCellStyle"
  655. :tree-config="treeProps"
  656. border="inner"
  657. height="600">
  658. <template #campaignName_default="{ row }">
  659. <template v-if="!row.adGroupId">
  660. <!-- 父节点(广告活动) -->
  661. <el-tag
  662. v-if="row.campaignType"
  663. :color="row.campaignType === 'sb' ? '#0163d2' : (row.campaignType === 'sp' ? '#ff7424' : '#365672')"
  664. class="campaign-type"
  665. disable-transitions
  666. round>
  667. {{ row.campaignType }}
  668. </el-tag>
  669. <span>{{ row.campaignName }}</span>
  670. </template>
  671. <template v-else>
  672. <!-- 子节点(广告组) -->
  673. <div class="flex-container">
  674. <span>{{ row.adGroupName }}</span>
  675. <el-button
  676. v-if="row.isSelected"
  677. :icon="DocumentAdd"
  678. class="btn-link"
  679. link
  680. @click="handleSelectTarget(row)">
  681. 共{{ row.targetLength }}个定向规则
  682. </el-button>
  683. </div>
  684. </template>
  685. </template>
  686. <template #header_operation>
  687. <el-button link size="default" style="color: #2077d7" @click="removeAllSelectedAds">删除全部</el-button>
  688. </template>
  689. <template #default_operation="{row}">
  690. <el-button type="text" @click="removeSelectedAd(row)">
  691. <CircleClose style="width:16px;color:#4b5765" />
  692. </el-button>
  693. </template>
  694. </vxe-grid>
  695. </div>
  696. </div>
  697. <template #footer>
  698. <div class="dialog-footer">
  699. <el-button @click="cancel">取消</el-button>
  700. <el-button type="primary" @click="confirm">确定</el-button>
  701. </div>
  702. </template>
  703. </el-dialog>
  704. <!--选择定向弹窗-->
  705. <TargetRuleDialog v-if="activeModel === 'specified'"
  706. v-model="targetRuleDialogVisible"
  707. :selectedTargetedRow="selectedTargetedRow"
  708. @confirm:targetRule="handleConfirm"
  709. ></TargetRuleDialog>
  710. </template>
  711. <style scoped>
  712. .pagination-container {
  713. display: flex;
  714. flex-direction: row-reverse;
  715. }
  716. .custom-inline {
  717. display: flex;
  718. justify-content: space-around;
  719. margin: 12px 0;
  720. gap: 4px;
  721. }
  722. .campaign-type {
  723. color: #fff;
  724. border-color: #fff;
  725. border-radius: 12px;
  726. margin-right: 4px;
  727. }
  728. .container {
  729. width: 100%;
  730. height: 100%;
  731. border: 1px solid #d6dbe2;
  732. border-radius: 4px;
  733. display: flex;
  734. overflow: hidden;
  735. align-content: center;
  736. flex-wrap: nowrap;
  737. flex-direction: row;
  738. /* padding: 10px; */
  739. }
  740. .container-left {
  741. width: 50%;
  742. border-right: 1px solid #d6dbe2;
  743. padding: 15px
  744. }
  745. .container-right {
  746. flex: 1;
  747. padding: 15px
  748. }
  749. .btn-link {
  750. font-size: 13px;
  751. color: #0085ff;
  752. }
  753. .flex-container {
  754. display: flex;
  755. justify-content: space-between;
  756. }
  757. </style>