adActivityDialog.vue 26 KB

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