view-target-rules.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. <script lang="ts" setup>
  2. /**
  3. * @Name: view-target-rules.vue
  4. * @Description: 点击选择定向按钮弹窗
  5. * @Author: xinyan
  6. */
  7. import { DocumentAdd } from '@element-plus/icons-vue';
  8. import { computed, onMounted, reactive, ref, watch } from 'vue';
  9. import { ElMessage } from 'element-plus';
  10. import { getCampaignRuleList } from '/@/views/components/auto/auto-campaigns/api';
  11. import SelectTarget from '/@/views/components/auto/auto-campaigns/select-target.vue';
  12. const props = defineProps({
  13. modelValue: {
  14. type: Boolean,
  15. required: true,
  16. },
  17. checkTarget: {
  18. type: Object,
  19. required: true,
  20. },
  21. });
  22. const emits = defineEmits(['update:modelValue']);
  23. const loading = ref(false);
  24. const xGridOne = ref(null);
  25. const xGridTwo = ref(null);
  26. let selectedAds = ref([]);
  27. const dialogVisible = ref(false);
  28. const SelectTargetVisible = ref(false);
  29. const selectedTargetedRow = ref(null);
  30. const selectedColumns = [
  31. {
  32. field: 'campaignName',
  33. title: '广告活动',
  34. slots: { default: 'campaignName_default' },
  35. treeNode: true
  36. },
  37. { title: '操作', width: 65, align: 'center', slots: { header: 'header_operation', default: 'default_operation' } }
  38. ];
  39. const treeProps = computed(() => {
  40. return {
  41. rowField: 'campaignId',
  42. childrenField: 'adGroupInfo', // 子节点字段
  43. expandAll: true,
  44. };
  45. });
  46. const gridOptions = reactive({
  47. border: 'inner',
  48. height: 500,
  49. loading: false,
  50. rowConfig: {
  51. isHover: true,
  52. keyField: 'campaignId',
  53. },
  54. treeConfig: treeProps,
  55. checkboxConfig: {
  56. labelField: 'name',
  57. reserve: true,
  58. checkStrictly: false,
  59. checkMethod: ({ row }) => {
  60. // 只允许取消选中
  61. return row.isSelected;
  62. }
  63. },
  64. columns: [
  65. {
  66. field: 'campaignName',
  67. title: '广告活动',
  68. slots: { default: 'campaignName_default' },
  69. treeNode: true
  70. },
  71. {
  72. type: 'checkbox',
  73. width: 55,
  74. fixed: 'right',
  75. slots: { header: 'checkbox_header', checkbox: 'checkbox_cell' }
  76. },
  77. ],
  78. data: []
  79. });
  80. function handleGridChange({ records, row, checked }) {
  81. console.log('=>(view-target-rules.vue:92) checked', checked);
  82. if (row) {
  83. if (!checked) {
  84. row.isSelected = false;
  85. console.log('=>(view-target-rules.vue:96) row.isSelected', row.isSelected);
  86. updateSelectedAds();
  87. }
  88. } else {
  89. // 全选/取消全选
  90. records.forEach(record => {
  91. if (record.isSelected) {
  92. record.isSelected = checked;
  93. }
  94. });
  95. updateSelectedAds();
  96. }
  97. }
  98. function toggleCheckboxEvent(row) {
  99. if (row.isSelected) {
  100. // 只有已选择的行可以被取消选中
  101. xGridOne.value.setCheckboxRow(row, false);
  102. handleGridChange({ records: [row], row, checked: false });
  103. }
  104. }
  105. async function fetchAdCampaign() {
  106. try {
  107. const resp = await getCampaignRuleList({
  108. profileId: props.checkTarget.profileId,
  109. campaignId: props.checkTarget.campaignId
  110. });
  111. if (resp.code === 2000 && resp.data.length > 0){
  112. // 计算并设置 targetLength
  113. resp.data.adGroupInfo = resp.data.adGroupInfo.map(group => ({
  114. ...group,
  115. targetLength: (group.selectTargetId?.length || 0) + (group.keywordInfo?.length || 0) + (group.campaignTargetInfo?.length || 0)
  116. }));
  117. gridOptions.data = resp.data;
  118. // 默认勾选有 selectTargetId 的数据
  119. const adGroupsToCheck = resp.data.adGroupInfo.filter(group =>
  120. group.selectTargetId && group.selectTargetId.length > 0
  121. );
  122. // 更新选中的广告
  123. selectedAds.value = [{
  124. campaignId: resp.data.campaignId,
  125. campaignType: resp.data.campaignType,
  126. campaignName: resp.data.campaignName,
  127. adGroupInfo: adGroupsToCheck
  128. }];
  129. // 在 nextTick 中设置选中状态,确保表格已经渲染
  130. nextTick(() => {
  131. if (xGridOne.value) {
  132. adGroupsToCheck.forEach(group => {
  133. group.isSelected = true;
  134. xGridOne.value.setCheckboxRow(group, true);
  135. });
  136. }
  137. });
  138. }
  139. } catch (error) {
  140. console.log("=>(view-target-rules.vue:158) error", error);
  141. } finally {
  142. // gridOptions.loading = false;
  143. }
  144. }
  145. function handleSelectTarget(row) {
  146. // 获取父节点数据
  147. const parent = gridOptions.data.find(campaign =>
  148. campaign.adGroupInfo.some(group => group.adGroupId === row.adGroupId)
  149. );
  150. selectedTargetedRow.value = {
  151. campaignType: parent.campaignType,
  152. campaignId: parent.campaignId,
  153. adGroupId: row.adGroupId,
  154. isSelected: row.isSelected || false,
  155. keywordInfo: row.keywordInfo || [],
  156. campaignTargetInfo: row.campaignTargetInfo || [],
  157. };
  158. SelectTargetVisible.value = true;
  159. }
  160. function updateSelectedAds() {
  161. selectedAds.value = gridOptions.data
  162. .filter(ad => ad.adGroupInfo && ad.adGroupInfo.some(group => group.isSelected))
  163. .map(ad => ({
  164. ...ad,
  165. adGroupInfo: ad.adGroupInfo.filter(group => group.isSelected),
  166. }));
  167. }
  168. // 选择定向弹窗确认按钮
  169. function handleConfirm({ campaignInfo, targetType }) {
  170. if (!selectedTargetedRow.value) return;
  171. // 找到父级广告活动
  172. const parentCampaign = gridOptions.data.find(campaign =>
  173. campaign.adGroupInfo.some(group => group.adGroupId === selectedTargetedRow.value.adGroupId)
  174. );
  175. if (parentCampaign) {
  176. // 更新子节点(广告组)的信息
  177. const group = parentCampaign.adGroupInfo.find(group => group.adGroupId === selectedTargetedRow.value.adGroupId);
  178. if (group) {
  179. if (targetType === 'keyword') {
  180. group.keywordInfo = campaignInfo;
  181. } else if (targetType === 'target') {
  182. group.campaignTargetInfo = campaignInfo;
  183. }
  184. group.isSelected = true; // 更新子节点的选择状态
  185. group.targetLength = (group.keywordInfo?.length || 0) + (group.campaignTargetInfo?.length || 0) + (group.selectTargetId?.length || 0);
  186. }
  187. // 勾选表格1中的对应行,只有在定向大于0时进行勾选
  188. if (group && group.targetLength > 0) {
  189. if (xGridOne.value) {
  190. xGridOne.value.setCheckboxRow(group, true); // 使用 setCheckboxRow 而不是 toggleCheckboxRow
  191. }
  192. }
  193. }
  194. // 更新选中的广告
  195. updateSelectedAds();
  196. }
  197. // 删除选中的广告
  198. const removeSelectedAd = async (row) => {
  199. const $grid = xGridTwo.value;
  200. if ($grid) {
  201. if (row.adGroupId) {
  202. // 删除子节点(广告组)
  203. selectedAds.value = selectedAds.value.map(ad => {
  204. if (ad.adGroupInfo) {
  205. return {
  206. ...ad,
  207. adGroupInfo: ad.adGroupInfo.filter(group => group.adGroupId !== row.adGroupId)
  208. };
  209. }
  210. return ad;
  211. }).filter(ad => ad.adGroupInfo && ad.adGroupInfo.length > 0);
  212. } else {
  213. // 删除父节点(广告活动)
  214. selectedAds.value = selectedAds.value.filter(ad => ad.campaignId !== row.campaignId);
  215. }
  216. await $grid.remove(row);
  217. }
  218. if (xGridOne.value) {
  219. await xGridOne.value.toggleCheckboxRow(row);
  220. }
  221. };
  222. function removeAllSelectedAds() {
  223. selectedAds.value = [];
  224. const $grid = xGridOne.value;
  225. if ($grid) {
  226. $grid.clearCheckboxRow();
  227. }
  228. }
  229. function confirm() {
  230. dialogVisible.value = false;
  231. }
  232. function cancel() {
  233. dialogVisible.value = false;
  234. }
  235. const headerCellStyle = (args) => {
  236. if (args.rowIndex === 0) {
  237. return {
  238. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  239. fontWeight: '500',
  240. };
  241. }
  242. };
  243. const cellStyle = () => {
  244. return {
  245. fontSize: '13px',
  246. //fontWeight: '500',
  247. };
  248. };
  249. watch(() => props.modelValue, (newValue) => {
  250. dialogVisible.value = newValue;
  251. });
  252. watch(dialogVisible, (newValue) => {
  253. emits('update:modelValue', newValue);
  254. fetchAdCampaign();
  255. });
  256. watch(props.checkTarget, () => {
  257. fetchAdCampaign();
  258. });
  259. onMounted(() => {
  260. // fetchAdCampaign();
  261. });
  262. </script>
  263. <template>
  264. <el-dialog
  265. v-model="dialogVisible"
  266. style="border-radius: 10px;"
  267. title="关联广告活动"
  268. width="1158px"
  269. >
  270. <div class="container">
  271. <div class="container-left">
  272. <vxe-grid ref="xGridOne" :cell-style="cellStyle" :header-cell-style="headerCellStyle" v-bind="gridOptions"
  273. @checkbox-change="handleGridChange" @checkbox-all="handleGridChange">
  274. <template #campaignName_default="{ row }">
  275. <el-tag
  276. v-if="row.campaignType"
  277. :color="row.campaignType === 'sb' ? '#0163d2' : (row.campaignType === 'sp' ? '#ff7424' : '#365672')"
  278. class="campaign-type"
  279. disable-transitions
  280. round>
  281. {{ row.campaignType }}
  282. </el-tag>
  283. <span> {{ row.campaignName }}</span>
  284. <span class="flex-container">
  285. {{ row.adGroupName }}
  286. <el-button
  287. v-if="row.adGroupName &&!row.isSelected"
  288. class="btn-link"
  289. link
  290. @click="handleSelectTarget(row)">
  291. 选择定向
  292. </el-button>
  293. <span v-else-if="row.adGroupName">已选择定向</span>
  294. </span>
  295. </template>
  296. <template #checkbox_header="{ checked, indeterminate }">
  297. <span class="custom-checkbox" @click.stop="toggleAllCheckboxEvent">
  298. <i v-if="indeterminate" class="vxe-icon-square-minus-fill" style="color: #0d84ff"></i>
  299. <i v-else-if="checked" class="vxe-icon-square-checked-fill" style="color: #0d84ff"></i>
  300. <i v-else class="vxe-icon-checkbox-unchecked" style="color: #0d84ff"></i>
  301. </span>
  302. </template>
  303. <template #checkbox_cell="{ row, checked, indeterminate }">
  304. <span class="custom-checkbox" @click.stop="toggleCheckboxEvent(row)">
  305. <i v-if="indeterminate" class="vxe-icon-square-minus-fill" style="color: #0d84ff"></i>
  306. <i v-else-if="checked" class="vxe-icon-square-checked-fill" style="color: #0d84ff"></i>
  307. <el-tooltip v-else
  308. class="box-item"
  309. content="请选择定向"
  310. effect="dark"
  311. placement="top"
  312. >
  313. <i class="vxe-icon-checkbox-unchecked" style="color: #0d84ff"></i>
  314. </el-tooltip>
  315. </span>
  316. </template>
  317. </vxe-grid>
  318. </div>
  319. <div class="container-right">
  320. <h3>已选择({{ selectedAds.length }})</h3>
  321. <vxe-grid ref="xGridTwo"
  322. :cell-style="cellStyle"
  323. :columns="selectedColumns"
  324. :data="selectedAds"
  325. :header-cell-style="headerCellStyle"
  326. :tree-config="treeProps"
  327. border="inner"
  328. height="484">
  329. <template #campaignName_default="{ row }">
  330. <template v-if="!row.adGroupId">
  331. <!-- 父节点(广告活动) -->
  332. <el-tag
  333. v-if="row.campaignType"
  334. :color="row.campaignType === 'sb' ? '#0163d2' : (row.campaignType === 'sp' ? '#ff7424' : '#365672')"
  335. class="campaign-type"
  336. disable-transitions
  337. round>
  338. {{ row.campaignType }}
  339. </el-tag>
  340. <span>{{ row.campaignName }}</span>
  341. </template>
  342. <template v-else>
  343. <!-- 子节点(广告组) -->
  344. <span class="flex-container">
  345. {{ row.adGroupName }}
  346. <el-button
  347. v-if="row.isSelected"
  348. :icon="DocumentAdd"
  349. class="btn-link"
  350. link
  351. @click="handleSelectTarget(row)">
  352. 共{{ row.targetLength }}个定向规则
  353. </el-button>
  354. </span>
  355. </template>
  356. </template>
  357. <template #header_operation>
  358. <el-button link size="default" style="color: #2077d7;font-size: 13px" @click="removeAllSelectedAds">
  359. 删除全部
  360. </el-button>
  361. </template>
  362. <template #default_operation="{row}">
  363. <el-button type="text" @click="removeSelectedAd(row)">
  364. <CircleClose style="width:16px;color:#4b5765" />
  365. </el-button>
  366. </template>
  367. </vxe-grid>
  368. </div>
  369. </div>
  370. <template #footer>
  371. <div class="dialog-footer">
  372. <el-button @click="cancel">取消</el-button>
  373. <el-button type="primary" @click="confirm">确定</el-button>
  374. </div>
  375. </template>
  376. </el-dialog>
  377. <SelectTarget v-model="SelectTargetVisible"
  378. :selectedTargetedRow="selectedTargetedRow"
  379. @confirm:selectTarget="handleConfirm"
  380. ></SelectTarget>
  381. </template>
  382. <style scoped>
  383. .container {
  384. width: 100%;
  385. height: 100%;
  386. border: 1px solid #d6dbe2;
  387. border-radius: 4px;
  388. display: flex;
  389. overflow: hidden;
  390. align-content: center;
  391. flex-wrap: nowrap;
  392. flex-direction: row;
  393. /* padding: 10px; */
  394. }
  395. .container-left {
  396. width: 50%;
  397. border-right: 1px solid #d6dbe2;
  398. padding: 15px
  399. }
  400. .container-right {
  401. flex: 1;
  402. padding: 15px
  403. }
  404. .campaign-type {
  405. color: #fff;
  406. border-color: #fff;
  407. border-radius: 12px;
  408. margin-right: 4px;
  409. }
  410. .btn-link {
  411. font-size: 13px;
  412. color: #0085ff;
  413. }
  414. .flex-container {
  415. display: flex;
  416. justify-content: space-between;
  417. }
  418. </style>