put-ad-plan.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. <template>
  2. <div class="page-wrap page-wrap-account page-adplan">
  3. <tool-bar
  4. :text="['ad_id', 'advertiser_id', 'campaign_id']"
  5. :label="['计划名/ID', '账号名/ID', '广告组名称/ID']"
  6. :defaultVal="defaultToolvalue"
  7. v-model:loading="inSearching"
  8. @confirm="onSearch"
  9. >
  10. <div class="tool-bar-item" v-if="optionList.length > 0">
  11. <p class="label">计划状态</p>
  12. <a-select class="full-width" v-model:value="currentSelect">
  13. <a-select-option
  14. v-for="item in optionList"
  15. :value="item.name"
  16. :key="item.name"
  17. >{{ item.desc }}</a-select-option
  18. >
  19. </a-select>
  20. </div>
  21. </tool-bar>
  22. <div class="table-filter">
  23. <div class="item-right">
  24. <span class="label">数据类型</span>
  25. <a-select class="full-width" v-model:value="currentStats">
  26. <a-select-option
  27. v-for="item in statsList"
  28. :value="item.name"
  29. :key="item.name"
  30. >{{ item.desc }}</a-select-option
  31. >
  32. </a-select>
  33. </div>
  34. <div class="item-right">
  35. <span class="label">筛选条件:</span>
  36. <a-range-picker
  37. v-model:value="pickerFilter"
  38. @change="switchDate"
  39. :ranges="rangePick"
  40. format="YYYY/MM/DD"
  41. />
  42. </div>
  43. <a-button type="primary" @click="openBackDrawer">
  44. 回本数据
  45. </a-button>
  46. <a-button type="primary" @click="openCustomLine">
  47. 自定义列
  48. </a-button>
  49. </div>
  50. <a-table
  51. :columns="columns"
  52. :data-source="list"
  53. :pagination="tablePageOptions"
  54. :loading="loading.value"
  55. @change="handleTableChange"
  56. rowKey="id"
  57. bordered
  58. :scroll="{ x: 1800, y: 700 }"
  59. >
  60. <template #switch="{ text, record }">
  61. <a-switch
  62. v-model:checked="record.enable"
  63. @change="switchMethod(record)"
  64. />
  65. </template>
  66. <template #external_url="{ text, record }">
  67. <p @click="onGo(record)"><a>前往落地页链接</a></p>
  68. </template>
  69. <template #ad_name="{ text, record }">
  70. <p>账户名:{{ record.account_name }}</p>
  71. <p>广告名:{{ record.ad_name }}</p>
  72. <p>广告ID:{{ record.ad_id }}</p>
  73. </template>
  74. <template #dayt="{ text, record }">
  75. <a-popover title="回传配置" placement="left" trigger="click">
  76. <template #content>
  77. <div class="tab-list" style="width: 220px">
  78. <a-tabs size="small" @change="tabChangeBack">
  79. <a-tab-pane
  80. v-for="(d, i) in popForm"
  81. :key="i"
  82. :tab="d.desc"
  83. ></a-tab-pane>
  84. </a-tabs>
  85. </div>
  86. <div class="hover-content">
  87. <span class="label">回传条件</span
  88. ><a-select
  89. style="width: 150px"
  90. size="small"
  91. v-model:value="popForm[currentTbs].condition"
  92. >
  93. <a-select-option
  94. :value="item.name"
  95. v-for="item in popForm[currentTbs].report_conditions"
  96. >
  97. {{ item.desc }}
  98. </a-select-option>
  99. </a-select>
  100. </div>
  101. <div class="hover-content">
  102. <span class="label">回传开关</span>
  103. <a-switch
  104. v-model:checked="popForm[currentTbs].back_on"
  105. checked-children="开"
  106. un-checked-children="关"
  107. />
  108. </div>
  109. <div class="hover-content">
  110. <span class="label">回传比例</span
  111. ><a-input
  112. v-model:value="popForm[currentTbs].rate"
  113. size="small"
  114. style="width: 150px"
  115. type="number"
  116. placeholder="回传比例"
  117. >
  118. <template #suffix>
  119. <a-tooltip title="比例0-100">
  120. <info-circle-outlined style="color: rgba(0,0,0,.45)" />
  121. </a-tooltip>
  122. </template>
  123. </a-input>
  124. </div>
  125. <div class="hover-content">
  126. <span class="label">回传平台</span
  127. ><a-input
  128. v-model:value="popForm[currentTbs].back_platform"
  129. disabled
  130. size="small"
  131. style="width: 150px"
  132. />
  133. </div>
  134. <div
  135. class="hover-content"
  136. v-if="popForm[currentTbs].report_type == 'recharge'"
  137. >
  138. <span class="label">回传付费最低金额</span
  139. ><a-input
  140. v-model:value="popForm[currentTbs].price"
  141. size="small"
  142. style="width: 150px"
  143. type="number"
  144. placeholder="回传付费最低金额"
  145. />
  146. </div>
  147. <div class="hover-content">
  148. <span class="label">浮动回传比例</span
  149. ><a-input
  150. v-model:value="popForm[currentTbs].float_rate"
  151. size="small"
  152. style="width: 150px"
  153. type="number"
  154. placeholder="浮动回传比例"
  155. >
  156. <template #suffix>
  157. <a-tooltip title="浮动比例小于固定回传比例">
  158. <info-circle-outlined style="color: rgba(0,0,0,.45)" />
  159. </a-tooltip>
  160. </template>
  161. </a-input>
  162. </div>
  163. <div class="footer-slot">
  164. <a-popconfirm
  165. title="是否要修改回传配置?"
  166. ok-text="是"
  167. cancel-text="否"
  168. @confirm="confirmEdit"
  169. >
  170. <a-button
  171. type="primary"
  172. size="small"
  173. :disabled="!hasPopFormData"
  174. @click="editBackConfig"
  175. >
  176. 修改
  177. </a-button>
  178. </a-popconfirm>
  179. </div>
  180. </template>
  181. <p @click="getBackData(record)"><a>回传</a></p>
  182. </a-popover>
  183. <p @click="getregister(record)"><a>注册用户</a></p>
  184. <p @click="getmoreLineData(record)"><a>更多数据</a></p>
  185. </template>
  186. <template #cpa_bid="{ text, record }">
  187. <editable-cell
  188. :text="`${text}`"
  189. title="预算"
  190. @change="(val) => onCellChange(record, 'cpa_bid', val)"
  191. />
  192. </template>
  193. <template #budget="{ text, record }">
  194. <editable-cell
  195. :text="`${text}`"
  196. title="出价"
  197. @change="(val) => onCellChange(record, 'budget', val)"
  198. />
  199. </template>
  200. <template #opertate="{ text, record }">
  201. <p @click="openDrawer(record)"><a>操作日志</a></p>
  202. </template>
  203. </a-table>
  204. <a-drawer
  205. title="操作日志"
  206. placement="right"
  207. :closable="false"
  208. v-model:visible="visible"
  209. >
  210. <put-data :id="currentId"></put-data>
  211. </a-drawer>
  212. <a-drawer
  213. title="回本数据"
  214. placement="right"
  215. :closable="false"
  216. v-model:visible="visible1"
  217. >
  218. <put-count
  219. :ids="backData.ids"
  220. :begin_date="backData.begin_date"
  221. :end_date="backData.end_date"
  222. :field="backData.field"
  223. ></put-count>
  224. </a-drawer>
  225. <a-drawer
  226. title="补充数据"
  227. placement="right"
  228. :closable="false"
  229. v-model:visible="lineVisable"
  230. >
  231. <a-descriptions bordered title="扩展数据" size="small">
  232. <a-descriptions-item :label="item.title" v-for="item in innerClomuns">
  233. {{ temData[item.dataIndex] }}
  234. </a-descriptions-item>
  235. </a-descriptions>
  236. </a-drawer>
  237. <a-drawer
  238. title="注册用户"
  239. placement="right"
  240. :closable="false"
  241. v-model:visible="registerVisable"
  242. >
  243. <register-datad
  244. :ad_lid="register.ad_lid"
  245. :back_platform="register.back_platform"
  246. ></register-datad>
  247. </a-drawer>
  248. <!-- <a-modal v-model:visible="columnShow" title="自定义列" @ok="makeDine" :width="722">
  249. <custom-cloumn></custom-cloumn>
  250. </a-modal> -->
  251. </div>
  252. </template>
  253. <script lang="ts">
  254. import { defineComponent, reactive, toRefs, ref, unref, onMounted } from "vue";
  255. import moment from "moment";
  256. import ToolBar from "@/components/tool-bar/index.vue";
  257. import PutData from "@/views/put/put-log.vue";
  258. import CustomCloumn from "@/views/put/component/customClomu.vue";
  259. import useApp from "@/hooks/useApp";
  260. import PutCount from "@/views/put/put-ad-count.vue";
  261. import RegisterDatad from "@/views/put/register-data.vue";
  262. import EditableCell from "@/components/edit-cell/index.vue";
  263. import usePagination from "@/hooks/usePagination";
  264. import { picker } from "@/helper/config/range";
  265. import { InfoCircleOutlined } from "@ant-design/icons-vue";
  266. import {
  267. TableColumnOfPutAdPlan,
  268. ALLCloumnList,
  269. } from "../_pageOptions/table-put";
  270. import {
  271. getADPlanlist,
  272. getCustomColumn,
  273. adChangeMoney,
  274. adChangeCrem,
  275. statusChange,
  276. getAddStatus,
  277. getAdBackPlan,
  278. setBackConfig,
  279. getAdStatus,
  280. } from "@/api";
  281. import { ADPlanItem, PageOptions, PlanBack } from "@/types/api";
  282. const PutAdPlan = defineComponent({
  283. components: {
  284. ToolBar,
  285. EditableCell,
  286. PutData,
  287. PutCount,
  288. InfoCircleOutlined,
  289. RegisterDatad,
  290. CustomCloumn
  291. },
  292. setup() {
  293. let { loading, meta, tablePageOptions } = usePagination();
  294. const { router, route } = useApp();
  295. let list: any[] = [],
  296. opList: any[] = [],
  297. stList: any = [];
  298. const state = reactive({
  299. platform: "platform1",
  300. list: ref<ADPlanItem[]>([]),
  301. inSearching: false,
  302. loading,
  303. currentSelect: "AD_STATUS_DELIVERY_OK",
  304. picker: [],
  305. currentId: "",
  306. innerClomuns: ref<any[]>([]),
  307. lineVisable: false,
  308. defaultToolvalue: {},
  309. visible: false,
  310. popconfirmShow: false,
  311. showPop: false,
  312. visible1: false,
  313. registerVisable: false,
  314. columnShow:false,
  315. register: {
  316. ad_lid: 0,
  317. back_platform: "",
  318. },
  319. pickerFilter: [moment(), moment()],
  320. tablePageOptions,
  321. columns: list,
  322. temData: {},
  323. backData: {
  324. ids: "",
  325. begin_date: "",
  326. end_date: "",
  327. field: "",
  328. },
  329. currentTbs: 0,
  330. hasPopFormData: false,
  331. popForm: [
  332. {
  333. id: 0,
  334. back_on: false,
  335. rate: 0,
  336. condition: "",
  337. price: 0,
  338. float_rate: 0,
  339. },
  340. ],
  341. isInit: false,
  342. cost_order: 0,
  343. optionList: opList,
  344. statsList: stList,
  345. currentStats: "paid_order_amount",
  346. defaultColumns: TableColumnOfPutAdPlan,
  347. fields: {},
  348. rangePick: picker,
  349. });
  350. if (route.query && route.query.advertiser_id) {
  351. state.defaultToolvalue = {
  352. campaign_id: route.query.campaign_id,
  353. };
  354. state.fields = {
  355. campaign_id: route.query.campaign_id,
  356. };
  357. }
  358. getAddStatus().then((res) => {
  359. res.data.unshift({
  360. name: "",
  361. desc: "不限",
  362. });
  363. state.optionList = res.data;
  364. });
  365. const onSearch = async (fields: Record<string, string>) => {
  366. try {
  367. const { ad_id, campaign_id, status } = fields;
  368. state.fields = fields;
  369. const data = {
  370. ad_id,
  371. status: state.currentSelect,
  372. campaign_id,
  373. page: 1,
  374. };
  375. getData(data);
  376. } catch (e) {
  377. console.log(e);
  378. } finally {
  379. state.inSearching = false;
  380. }
  381. };
  382. const switchDate = (date: any, dateString: string) => {
  383. onSearch(state.fields);
  384. };
  385. const getData = (query?: any) => {
  386. const { pickerFilter } = state;
  387. let [begin_dates, end_dates] = pickerFilter;
  388. let begin_date = moment(begin_dates).format("YYYY-MM-DD");
  389. let end_date = moment(end_dates).format("YYYY-MM-DD");
  390. let data = Object.assign(
  391. {
  392. page: 1,
  393. status: "AD_STATUS_DELIVERY_OK",
  394. ...state.fields,
  395. },
  396. query || {},
  397. { begin_date, end_date, status: state.currentSelect },
  398. state.cost_order ? { cost_order: state.cost_order } : {}
  399. );
  400. getADPlanlist(data).then((res) => {
  401. let newList: any[] = res.data.list.map((item) => {
  402. item.enable = item.is_enable == 1 ? true : false;
  403. item.popShow = false;
  404. return item;
  405. });
  406. state.list = newList;
  407. meta.value = res.data.meta;
  408. state.inSearching = false;
  409. });
  410. };
  411. getAdStatus().then((res) => {
  412. state.statsList = res.data;
  413. });
  414. getCustomColumn().then((res) => {
  415. let columns: any[] = [];
  416. let blackList = [
  417. "email",
  418. "ad_name",
  419. "account_name",
  420. "ad_id",
  421. "delivery_platform",
  422. ];
  423. let extendList = [
  424. "campaign_name",
  425. "landing_type",
  426. "start_end_time",
  427. "pricing",
  428. "promotion_type",
  429. "delivery_range",
  430. "inventory_type",
  431. "convert_id",
  432. "external_actions",
  433. "ad_create_time",
  434. ];
  435. res.data.map((item: { desc: string; name: string }) => {
  436. let lolumnItem: {
  437. title: string;
  438. dataIndex: string;
  439. slots?: any;
  440. width?: string | number;
  441. sorter?: boolean;
  442. ellipsis?: boolean;
  443. } = {
  444. title: item.desc,
  445. dataIndex: item.name,
  446. width: 95,
  447. ellipsis: true,
  448. };
  449. if (item.name == "external_url") {
  450. lolumnItem.slots = { customRender: "external_url" };
  451. }
  452. if (item.name == "cpa_bid" || item.name == "budget") {
  453. lolumnItem.slots = { customRender: item.name };
  454. lolumnItem.width = 130;
  455. }
  456. if (item.name == "cost") {
  457. lolumnItem.sorter = true;
  458. }
  459. if (extendList.includes(item.name)) {
  460. state.innerClomuns.push(lolumnItem);
  461. }
  462. columns.push(lolumnItem);
  463. });
  464. let newColunms = columns.filter(
  465. (item) =>
  466. !blackList.includes(item.dataIndex) &&
  467. !extendList.includes(item.dataIndex)
  468. );
  469. state.columns = [];
  470. state.columns.push(...state.defaultColumns);
  471. state.columns.push(...newColunms);
  472. state.columns.push({
  473. title: "操作记录",
  474. dataIndex: "opertate",
  475. slots: { customRender: "opertate" },
  476. width: 100,
  477. });
  478. state.columns.push({
  479. title: "日志",
  480. dataIndex: "opertate",
  481. fixed: "right",
  482. slots: { customRender: "dayt" },
  483. width: 100,
  484. });
  485. });
  486. const setSateSwitch = (val: string, name: string) => {
  487. switch (val) {
  488. case "ascend":
  489. (state as any)[name] = 2;
  490. break;
  491. case "descend":
  492. (state as any)[name] = 1;
  493. break;
  494. default:
  495. (state as any)[name] = 0;
  496. }
  497. };
  498. const handleTableChange = (
  499. pagination: PageOptions,
  500. filters: any,
  501. sorter: any
  502. ) => {
  503. if (sorter.columnKey == "cost") {
  504. setSateSwitch(sorter.order, "cost_order");
  505. }
  506. const { current, pageSize, total } = pagination;
  507. getData({ page: current });
  508. };
  509. onMounted(() => {
  510. getData({
  511. campaign_id: route.query?.campaign_id ?? "",
  512. current: 1,
  513. });
  514. });
  515. return { ...toRefs(state), handleTableChange, onSearch, switchDate };
  516. },
  517. methods: {
  518. moment,
  519. onGo(record: any) {
  520. window.open(record.external_url);
  521. },
  522. getTest() {},
  523. openDrawer(record: any) {
  524. this.visible = true;
  525. this.currentId = record.ad_id;
  526. },
  527. handleVisibleChange(visibale: boolean) {
  528. if (!visibale) {
  529. this.popconfirmShow = false;
  530. return;
  531. }
  532. if (!this.hasPopFormData) {
  533. this.popconfirmShow = false;
  534. } else {
  535. this.popconfirmShow = true;
  536. }
  537. },
  538. getmoreLineData(record: any) {
  539. this.lineVisable = true;
  540. this.temData = record;
  541. },
  542. confirmEdit() {
  543. let { id, back_on, rate, condition, price, float_rate } = this.popForm[
  544. this.currentTbs
  545. ];
  546. if (float_rate && float_rate >= rate) {
  547. this.$message.error("浮动比例必须小于固定回传比例");
  548. return;
  549. }
  550. let data = {
  551. id,
  552. back_on: Number(back_on),
  553. rate,
  554. condition,
  555. price,
  556. float_rate,
  557. };
  558. setBackConfig(data).then((res) => {
  559. this.$message.success("修改成功!");
  560. });
  561. },
  562. getregister(record: any) {
  563. console.log(record);
  564. this.register.ad_lid = record.id;
  565. this.register.back_platform = record.delivery_platform;
  566. this.registerVisable = true;
  567. },
  568. onCellChange(record: any, dataIndex: string, value: string) {
  569. let ad_id = record.ad_id;
  570. if (dataIndex == "cpa_bid") {
  571. adChangeCrem({ ad_id, bid: Number(value) })
  572. .then((res) => {
  573. this.$message.success("修改成功!");
  574. })
  575. .catch((e) => {
  576. //location.reload();
  577. });
  578. }
  579. if (dataIndex == "budget") {
  580. adChangeMoney({ ad_id, budget: Number(value) })
  581. .then((res) => {
  582. this.$message.success("修改成功!");
  583. })
  584. .catch((e) => {
  585. //location.reload();
  586. });
  587. }
  588. },
  589. tabChangeBack(key: number) {
  590. this.currentTbs = key;
  591. },
  592. editBackConfig() {},
  593. switchMethod(record: any) {
  594. let ad_id = record.ad_id;
  595. statusChange({
  596. ad_id,
  597. status: record.enable ? "disable" : "enable",
  598. }).then((res) => {
  599. this.$message.success("修改广告状态成功!");
  600. });
  601. },
  602. openBackDrawer() {
  603. let ids = "";
  604. this.list.map((item: ADPlanItem) => {
  605. ids = ids + `,${item.id}`;
  606. });
  607. let [begin_dates, end_dates] = this.pickerFilter;
  608. let begin_date = moment(begin_dates).format("YYYY-MM-DD");
  609. let end_date = moment(end_dates).format("YYYY-MM-DD");
  610. this.backData.ids = ids.substring(1);
  611. this.backData.begin_date = begin_date;
  612. this.backData.end_date = end_date;
  613. this.backData.field = this.currentStats;
  614. this.visible1 = true;
  615. },
  616. getBackData(record: any) {
  617. getAdBackPlan({
  618. ad_lid: record.id,
  619. back_platform: record.delivery_platform,
  620. }).then((res) => {
  621. let list = res.data.map((r: PlanBack) => {
  622. r.price = r.extra?.price;
  623. r.float_rate = r.extra?.float_rate;
  624. r.back_on = !!Number(r.back_on);
  625. return r;
  626. });
  627. list.length > 0
  628. ? (this.hasPopFormData = true)
  629. : (this.hasPopFormData = false);
  630. this.popForm = list;
  631. });
  632. },
  633. },
  634. });
  635. export default PutAdPlan;
  636. </script>
  637. <style lang="scss">
  638. .table-filter {
  639. display: flex;
  640. justify-content: flex-end;
  641. padding: 5px 0 15px;
  642. align-items: center;
  643. }
  644. .ant-drawer-content-wrapper {
  645. width: 80vw !important;
  646. }
  647. .item-right {
  648. margin-right: 10px;
  649. display: flex;
  650. align-items: center;
  651. .ant-select {
  652. width: 100px;
  653. }
  654. .label {
  655. display: inline-block;
  656. min-width: 80px;
  657. }
  658. }
  659. .hover-content {
  660. margin: 8px 0 5px;
  661. display: flex;
  662. align-items: center;
  663. .label {
  664. padding-right: 15px;
  665. display: inline-block;
  666. max-width: 72px;
  667. width: 72px;
  668. }
  669. .ant-switch {
  670. width: 50px;
  671. }
  672. }
  673. .footer-slot {
  674. margin-top: 10px;
  675. button {
  676. margin-right: 10px;
  677. }
  678. }
  679. </style>