pushReport.vue 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['menu.pushReport', 'menu.pushReport.list']" />
  4. <a-card class="general-card" :title="$t('menu.pushReport.list')">
  5. <a-row>
  6. <a-col :flex="1">
  7. <a-form :model="formModel" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
  8. label-align="left">
  9. <a-row :gutter="16">
  10. <a-col :span="6">
  11. <a-form-item field="type" label="投放场景">
  12. <a-select v-model="formModel.type" :options="typeOptions" placeholder="请选择投放场景"
  13. @change="onTypeOrDateChange" />
  14. </a-form-item>
  15. </a-col>
  16. <a-col :span="6">
  17. <a-form-item field="report_date" label="报告日期">
  18. <a-date-picker v-model="formModel.report_date" style="width: 100%;"
  19. value-format="YYYY-MM-DD" @change="onTypeOrDateChange" />
  20. </a-form-item>
  21. </a-col>
  22. <a-col :span="6">
  23. <a-form-item field="platform" label="平台">
  24. <div class="filter-with-clear">
  25. <a-select v-model="formModel.platform" :options="platformOptions"
  26. placeholder="请选择平台" allow-search :filter-option="filterPlatformOption"
  27. @change="onTypeOrDateChange" style="width: 100%;">
  28. </a-select>
  29. <a-button v-if="formModel.platform" type="text" class="clear-btn"
  30. @click="clearPlatform">
  31. <icon-close />
  32. </a-button>
  33. </div>
  34. </a-form-item>
  35. </a-col>
  36. <a-col :span="6">
  37. <a-form-item field="status" label="推送状态">
  38. <a-select v-model="formModel.status" :options="statusOptions" placeholder="请选择推送状态"
  39. @change="onTypeOrDateChange">
  40. </a-select>
  41. </a-form-item>
  42. </a-col>
  43. </a-row>
  44. <a-row :gutter="16">
  45. <a-col :span="6">
  46. <a-form-item field="is_settlement" label="结算状态">
  47. <a-select v-model="formModel.is_settlement" :options="settlementOptions"
  48. placeholder="请选择结算状态" @change="onTypeOrDateChange">
  49. </a-select>
  50. </a-form-item>
  51. </a-col>
  52. </a-row>
  53. </a-form>
  54. </a-col>
  55. <a-divider style="height: 38px" direction="vertical" />
  56. <a-col :flex="'86px'" style="text-align: right">
  57. <a-space direction="vertical" :size="18">
  58. <a-button type="primary" @click="search">
  59. <template #icon>
  60. <icon-search />
  61. </template>
  62. {{ $t('searchTable.form.search') }}
  63. </a-button>
  64. </a-space>
  65. </a-col>
  66. </a-row>
  67. <a-divider style="margin-top: 0" />
  68. <a-row style="margin-bottom: 16px">
  69. <a-col :span="12">
  70. <a-space>
  71. <a-button type="primary" @click="onAdd">
  72. <template #icon>
  73. <icon-plus />
  74. </template>
  75. 新增数据
  76. </a-button>
  77. <a-button type="success" :disabled="selectedRowKeys.length === 0" @click="onBatchPush">
  78. <template #icon>
  79. <icon-upload />
  80. </template>
  81. 批量推送 ({{ selectedRowKeys.length }})
  82. </a-button>
  83. <a-button type="danger" :disabled="selectedRowKeys.length === 0" @click="onBatchDelete">
  84. <template #icon>
  85. <icon-delete />
  86. </template>
  87. 批量删除 ({{ selectedRowKeys.length }})
  88. </a-button>
  89. </a-space>
  90. </a-col>
  91. <a-col :span="12" style="
  92. display: flex;
  93. align-items: center;
  94. justify-content: end;
  95. ">
  96. <a-tooltip :content="$t('searchTable.actions.refresh')">
  97. <div class="action-icon" @click="search"><icon-refresh size="18" /></div>
  98. </a-tooltip>
  99. <a-dropdown @select="handleSelectDensity">
  100. <a-tooltip :content="$t('searchTable.actions.density')">
  101. <div class="action-icon"><icon-line-height size="18" /></div>
  102. </a-tooltip>
  103. <template #content>
  104. <a-doption v-for="item in densityList" :key="item.value" :value="item.value"
  105. :class="{ active: item.value === size }">
  106. <span>{{ item.name }}</span>
  107. </a-doption>
  108. </template>
  109. </a-dropdown>
  110. <a-tooltip :content="$t('searchTable.actions.columnSetting')">
  111. <a-popover trigger="click" position="bl" @popup-visible-change="popupVisibleChange">
  112. <div class="action-icon"><icon-settings size="18" /></div>
  113. <template #content>
  114. <div id="tableSetting">
  115. <div v-for="(item, index) in showColumns" :key="item.dataIndex" class="setting">
  116. <div style="
  117. margin-right: 4px;
  118. cursor: move;
  119. ">
  120. <icon-drag-arrow />
  121. </div>
  122. <div>
  123. <a-checkbox v-model="item.checked" @change="
  124. handleChange(
  125. $event,
  126. item as TableColumnData,
  127. index
  128. )
  129. ">
  130. </a-checkbox>
  131. </div>
  132. <div class="title">
  133. {{
  134. item.title === '#'
  135. ? '序列号'
  136. : item.title
  137. }}
  138. </div>
  139. </div>
  140. </div>
  141. </template>
  142. </a-popover>
  143. </a-tooltip>
  144. </a-col>
  145. </a-row>
  146. <a-table row-key="id" :loading="loading" :pagination="pagination"
  147. :columns="(cloneColumns as TableColumnData[])" :data="renderData" :bordered="{ headerCell: true }"
  148. :size="size" @page-change="onPageChange" :scroll="{ x: 1700, y: tableHeight }"
  149. v-model:selectedKeys="selectedRowKeys" :row-selection="{
  150. type: 'checkbox',
  151. showCheckedAll: true
  152. }">
  153. <template #index="{ record }">
  154. {{ record.id }}
  155. </template>
  156. <template #type="{ record }">
  157. <div>{{ getTypeName(record.type) }}</div>
  158. <a-typography-text type="secondary">{{ getSubTypeName(record.sub_type) }}
  159. </a-typography-text>
  160. </template>
  161. <template #date="{ record }">
  162. {{ dayjs(record.report_date).format('YYYY-MM-DD') }}
  163. </template>
  164. <template #platform="{ record }">
  165. {{ getPlatformNameById(record.platform) }}
  166. </template>
  167. <template #adId="{ record }">
  168. <div v-if="record.adId">
  169. 广告Id:<a-tag color="blue" bordered> {{ record.adId }} </a-tag>
  170. </div>
  171. <div v-if="record.adzoneId">
  172. 策略Id:<a-tag color="blue" bordered> {{ record.adzoneId }} </a-tag>
  173. </div>
  174. </template>
  175. <template #status="{ record }">
  176. <div>
  177. <a-tag :color="getStatusColor(record.status)" bordered>
  178. {{ getStatusText(record.status) }}
  179. </a-tag>
  180. <div class="status-extra-info" v-if="record.last_push_time || record.request_id">
  181. <div v-if="record.request_id" class="text-ellipsis">
  182. <span class="text-gray">标识: </span>
  183. {{ record.request_id }}
  184. </div>
  185. </div>
  186. </div>
  187. </template>
  188. <template #time="{ record }">
  189. <div style="color:gray;">
  190. <div>添加:{{ dayjs(record.create_time).format('MM-DD HH:mm') }}</div>
  191. <div>更新:{{ dayjs(record.update_time).format('MM-DD HH:mm') }}</div>
  192. <div v-if="record.last_push_time">推送:{{ dayjs(record.last_push_time).format('MM-DD HH:mm') }}
  193. </div>
  194. </div>
  195. </template>
  196. <template #request_metrics="{ record }">
  197. <div>请求量: <template v-if="record.req_count == 0 || record.req_count == null">-</template><a-tag
  198. v-else color="green">{{ record.req_count }}</a-tag></div>
  199. <div>DP转链: <template
  200. v-if="record.dp_transition_succ_count == 0 || record.dp_transition_succ_count == null">-</template><a-tag
  201. v-else color="green">{{ record.dp_transition_succ_count }}</a-tag></div>
  202. <div>商业转链: <template
  203. v-if="record.transition_succ_count == 0 || record.transition_succ_count == null">-</template><a-tag
  204. v-else color="green">{{ record.transition_succ_count }}</a-tag></div>
  205. </template>
  206. <template #expose_metrics="{ record }">
  207. <div>曝光PV: <template
  208. v-if="record.expose_count == 0 || record.expose_count == null">-</template><a-tag v-else
  209. color="green">{{ record.expose_count }}</a-tag></div>
  210. <div>曝光UV: <template
  211. v-if="record.expose_user_count == 0 || record.expose_user_count == null">-</template><a-tag
  212. v-else color="green">{{ record.expose_user_count }}</a-tag></div>
  213. </template>
  214. <template #click_metrics="{ record }">
  215. <div>点击PV: <template v-if="record.click_count == 0 || record.click_count == null">-</template><a-tag
  216. v-else color="green">{{ record.click_count }}</a-tag></div>
  217. <div>点击UV: <template
  218. v-if="record.click_user_count == 0 || record.click_user_count == null">-</template><a-tag
  219. v-else color="green">{{ record.click_user_count }}</a-tag></div>
  220. </template>
  221. <template #app_metrics="{ record }">
  222. <div>唤端PV: <template
  223. v-if="record.app_open_count == 0 || record.app_open_count == null">-</template><a-tag v-else
  224. color="green">{{ record.app_open_count }}</a-tag></div>
  225. <div>唤端UV: <template
  226. v-if="record.app_open_user_count == 0 || record.app_open_user_count == null">-</template><a-tag
  227. v-else color="green">{{ record.app_open_user_count }}</a-tag></div>
  228. </template>
  229. <template #order_metrics="{ record }">
  230. <div>订单PV: <template v-if="record.order_count == 0 || record.order_count == null">-</template><a-tag
  231. v-else color="green">{{ record.order_count }}</a-tag></div>
  232. <div>订单UV: <template
  233. v-if="record.order_user_count == 0 || record.order_user_count == null">-</template><a-tag
  234. v-else color="green">{{ record.order_user_count }}</a-tag></div>
  235. </template>
  236. <template #income_metrics="{ record }">
  237. <div>GMV: <template v-if="record.gmv == 0 || record.gmv == null">-</template><a-tag v-else
  238. color="green">{{ record.gmv }}元</a-tag></div>
  239. <div>预估收入: <template v-if="record.income == 0 || record.income == null">-</template><a-tag v-else
  240. color="green">{{
  241. record.income }}元</a-tag></div>
  242. </template>
  243. <template #settlement="{ record }">
  244. <a-tag :color="record.is_settlement ? 'green' : 'orange'" bordered>
  245. {{ record.is_settlement ? '已结算' : '未结算' }}
  246. </a-tag>
  247. </template>
  248. <template #command="{ record }">
  249. <a-space>
  250. <a-button type="primary" @click="onEdit(record)">
  251. 编辑
  252. </a-button>
  253. <a-button status="danger" @click="onDelete(record)">
  254. 删除
  255. </a-button>
  256. <a-button status="success" @click="onPush(record)">
  257. 推送
  258. </a-button>
  259. </a-space>
  260. </template>
  261. </a-table>
  262. </a-card>
  263. <a-modal v-model:visible="visible" :title="isEdit ? '修改数据' : '新建数据'" @ok="handleSubmit" @cancel="handleCancel"
  264. width="800px">
  265. <a-form :model="editForm" layout="vertical">
  266. <a-row :gutter="24">
  267. <a-col :span="12">
  268. <a-form-item label="报告日期" required>
  269. <a-date-picker v-model="editForm.report_date" style="width: 100%;" value-format="YYYY-MM-DD"
  270. format="YYYY-MM-DD" show-time-default-value="00:00:00" />
  271. </a-form-item>
  272. </a-col>
  273. <a-col :span="12">
  274. <a-form-item label="是否结算" required>
  275. <a-switch v-model="editForm.is_settlement" :checked-value="true" :unchecked-value="false"
  276. checked-text="已结算" unchecked-text="未结算" />
  277. </a-form-item>
  278. </a-col>
  279. <a-col :span="12">
  280. <a-form-item label="投放场景" required>
  281. <a-select v-model="editForm.type" :options="typeOptions" placeholder="请选择投放场景" />
  282. </a-form-item>
  283. </a-col>
  284. <a-col :span="12">
  285. <a-form-item label="子场景" required>
  286. <a-select v-model="editForm.sub_type" :options="subTypeOptions" placeholder="请选择子场景"
  287. allow-create allow-search />
  288. </a-form-item>
  289. </a-col>
  290. <a-col :span="8">
  291. <a-form-item label="请求量" required>
  292. <a-input-number v-model="editForm.req_count" placeholder="请输入请求量" :min="0"
  293. style="width: 100%;" />
  294. </a-form-item>
  295. </a-col>
  296. <a-col :span="8">
  297. <a-form-item label="DP转链成功数" required>
  298. <a-input-number v-model="editForm.dp_transition_succ_count" placeholder="请输入DP转链成功数"
  299. :min="0" style="width: 100%;" />
  300. </a-form-item>
  301. </a-col>
  302. <a-col :span="8">
  303. <a-form-item label="商业转链成功数" required>
  304. <a-input-number v-model="editForm.transition_succ_count" placeholder="请输入商业转链成功数" :min="0"
  305. style="width: 100%;" />
  306. </a-form-item>
  307. </a-col>
  308. <a-col :span="6">
  309. <a-form-item label="曝光PV" required>
  310. <a-input-number v-model="editForm.expose_count" placeholder="请输入曝光PV" :min="0"
  311. style="width: 100%;" />
  312. </a-form-item>
  313. </a-col>
  314. <a-col :span="6">
  315. <a-form-item label="曝光UV" required>
  316. <a-input-number v-model="editForm.expose_user_count" placeholder="请输入曝光UV" :min="0"
  317. style="width: 100%;" />
  318. </a-form-item>
  319. </a-col>
  320. <a-col :span="6">
  321. <a-form-item label="点击PV" required>
  322. <a-input-number v-model="editForm.click_count" placeholder="请输入点击PV" :min="0"
  323. style="width: 100%;" />
  324. </a-form-item>
  325. </a-col>
  326. <a-col :span="6">
  327. <a-form-item label="点击UV" required>
  328. <a-input-number v-model="editForm.click_user_count" placeholder="请输入点击UV" :min="0"
  329. style="width: 100%;" />
  330. </a-form-item>
  331. </a-col>
  332. <a-col :span="6">
  333. <a-form-item label="唤端PV" required>
  334. <a-input-number v-model="editForm.app_open_count" placeholder="请输入唤端PV" :min="0"
  335. style="width: 100%;" />
  336. </a-form-item>
  337. </a-col>
  338. <a-col :span="6">
  339. <a-form-item label="唤端UV" required>
  340. <a-input-number v-model="editForm.app_open_user_count" placeholder="请输入唤端UV" :min="0"
  341. style="width: 100%;" />
  342. </a-form-item>
  343. </a-col>
  344. <a-col :span="6">
  345. <a-form-item label="订单PV" required>
  346. <a-input-number v-model="editForm.order_count" placeholder="请输入订单PV" :min="0"
  347. style="width: 100%;" />
  348. </a-form-item>
  349. </a-col>
  350. <a-col :span="6">
  351. <a-form-item label="订单UV" required>
  352. <a-input-number v-model="editForm.order_user_count" placeholder="请输入订单UV" :min="0"
  353. style="width: 100%;" />
  354. </a-form-item>
  355. </a-col>
  356. <a-col :span="12">
  357. <a-form-item label="GMV(元)" required>
  358. <a-input-number v-model="editForm.gmv" placeholder="请输入GMV" :min="0" :precision="2"
  359. style="width: 100%;" />
  360. </a-form-item>
  361. </a-col>
  362. <a-col :span="12">
  363. <a-form-item label="预估收入(元)" required>
  364. <a-input-number v-model="editForm.income" placeholder="请输入预估收入" :min="0" :precision="2"
  365. style="width: 100%;" />
  366. </a-form-item>
  367. </a-col>
  368. <a-col :span="12">
  369. <a-form-item label="平台" required>
  370. <a-select v-model="editForm.platform" :options="platformOptions" placeholder="请选择平台"
  371. allow-search :filter-option="filterPlatformOption" />
  372. </a-form-item>
  373. </a-col>
  374. <a-col :span="12">
  375. <a-form-item label="推送状态">
  376. <a-select v-model="editForm.status" :options="[
  377. { label: '未推送', value: 0 },
  378. { label: '推送成功', value: 1 },
  379. { label: '推送失败', value: 2 },
  380. { label: '手动推送', value: 3 }
  381. ]">
  382. <template #label="{ data }">
  383. <span :style="{ color: getStatusColor(data.value) }">{{ data.label }}</span>
  384. </template>
  385. </a-select>
  386. </a-form-item>
  387. </a-col>
  388. <a-col :span="12">
  389. <a-form-item label="广告Id">
  390. <a-input v-model="editForm.adId" placeholder="请输入广告Id" />
  391. </a-form-item>
  392. </a-col>
  393. <a-col :span="12">
  394. <a-form-item label="分流id/策略id">
  395. <a-input v-model="editForm.adzoneId" placeholder="请输入分流id/策略id" />
  396. </a-form-item>
  397. </a-col>
  398. </a-row>
  399. </a-form>
  400. </a-modal>
  401. <a-modal v-model:visible="batchPushModalVisible" :title="batchPushModalLoading ? '推送中' : '推送完成'"
  402. :mask-closable="false" :closable="!batchPushModalLoading" @before-ok="handleBatchPushComplete" ok-text="完成"
  403. :ok-button-props="{ disabled: batchPushModalLoading }" :cancel-button-props="{ disabled: true }">
  404. <div style="text-align: center; padding: 20px 0;">
  405. <div v-if="batchPushModalLoading">
  406. <a-spin />
  407. <div style="margin-top: 15px; font-size: 16px;">
  408. {{ batchPushProgress }}/{{ batchPushTotal }}条数据已处理
  409. </div>
  410. <div style="margin-top: 10px;">
  411. 成功:<span style="color: #00b42a; font-weight: bold;">{{ batchPushSuccess }}</span>
  412. 失败:<span style="color: #f53f3f; font-weight: bold;">{{ batchPushFail }}</span>
  413. </div>
  414. <div style="margin-top: 20px; color: #86909c;">
  415. 处理中,请稍候...
  416. </div>
  417. </div>
  418. <div v-else>
  419. <a-result status="success" title="推送完成">
  420. <template #subtitle>
  421. <div>
  422. 共处理 {{ batchPushTotal }} 条数据,
  423. 成功:<span style="color: #00b42a; font-weight: bold;">{{ batchPushSuccess }}</span>
  424. 失败:<span style="color: #f53f3f; font-weight: bold;">{{ batchPushFail }}</span>
  425. </div>
  426. </template>
  427. </a-result>
  428. </div>
  429. </div>
  430. </a-modal>
  431. </div>
  432. </template>
  433. <script lang="ts" setup>
  434. import { useRouter } from 'vue-router';
  435. import { computed, ref, reactive, watch, nextTick, onMounted, onUnmounted } from 'vue';
  436. import { useI18n } from 'vue-i18n';
  437. import dayjs from 'dayjs';
  438. import useLoading from '@/hooks/loading';
  439. import { Pagination } from '@/types/global';
  440. import { Modal, Message } from '@arco-design/web-vue';
  441. import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
  442. import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
  443. import cloneDeep from 'lodash/cloneDeep';
  444. import Sortable from 'sortablejs';
  445. import {
  446. PushDataReportParams, PushDataReportRecord, queryReportList, saveReport, pushReport, deleteReport
  447. } from '@/api/dataReport';
  448. import { platformList, getPlatformNameById } from '@/constants/pushPlatform';
  449. type SizeProps = 'mini' | 'small' | 'medium' | 'large';
  450. type Column = TableColumnData & { checked?: true };
  451. const generateFormModel = () => {
  452. return {
  453. type: '',
  454. platform: '',
  455. status: -1,
  456. is_settlement: -1,
  457. report_date: dayjs().format('YYYY-MM-DD'),
  458. sort: 'report_date',
  459. order: 'descending',
  460. };
  461. };
  462. const router = useRouter();
  463. const { loading, setLoading } = useLoading(true);
  464. const { t } = useI18n();
  465. const renderData = ref<PushDataReportRecord[]>([]);
  466. const formModel = ref(generateFormModel());
  467. const cloneColumns = ref<Column[]>([]);
  468. const showColumns = ref<Column[]>([]);
  469. const size = ref<SizeProps>('small');
  470. const basePagination: Pagination = {
  471. current: 1,
  472. pageSize: 50,
  473. };
  474. const pagination = reactive({
  475. ...basePagination,
  476. });
  477. const typeOptions = computed<SelectOptionData[]>(() => [
  478. {
  479. label: '全部',
  480. value: '',
  481. },
  482. {
  483. label: '口令',
  484. value: 1,
  485. },
  486. {
  487. label: 'SDK优惠券',
  488. value: 2,
  489. },
  490. {
  491. label: '应用建议卡',
  492. value: 3,
  493. },
  494. {
  495. label: '购物助手',
  496. value: 4,
  497. },
  498. {
  499. label: '侧边栏',
  500. value: 7,
  501. },
  502. {
  503. label: '搜同款',
  504. value: 10,
  505. },
  506. ]);
  507. const densityList = computed(() => [
  508. {
  509. name: t('searchTable.size.mini'),
  510. value: 'mini',
  511. },
  512. {
  513. name: t('searchTable.size.small'),
  514. value: 'small',
  515. },
  516. {
  517. name: t('searchTable.size.medium'),
  518. value: 'medium',
  519. },
  520. {
  521. name: t('searchTable.size.large'),
  522. value: 'large',
  523. },
  524. ]);
  525. // 推送状态选项
  526. const statusOptions = computed<SelectOptionData[]>(() => [
  527. {
  528. label: '全部状态',
  529. value: -1,
  530. },
  531. {
  532. label: '未推送',
  533. value: 0,
  534. },
  535. {
  536. label: '推送成功',
  537. value: 1,
  538. },
  539. {
  540. label: '推送失败',
  541. value: 2,
  542. },
  543. {
  544. label: '手动推送',
  545. value: 3,
  546. },
  547. ]);
  548. // 结算状态选项
  549. const settlementOptions = computed<SelectOptionData[]>(() => [
  550. {
  551. label: '全部状态',
  552. value: -1,
  553. },
  554. {
  555. label: '已结算',
  556. value: 1,
  557. },
  558. {
  559. label: '未结算',
  560. value: 0,
  561. },
  562. ]);
  563. // 获取投放场景名称
  564. const getTypeName = (type: number) => {
  565. switch (type) {
  566. case 1:
  567. return '口令';
  568. case 2:
  569. return 'SDK优惠券';
  570. case 3:
  571. return '应用建议卡';
  572. case 4:
  573. return '购物助手';
  574. case 7:
  575. return '侧边栏';
  576. case 10:
  577. return '搜同款';
  578. default:
  579. return `未知(${type})`;
  580. }
  581. };
  582. // 获取子场景名称
  583. const getSubTypeName = (subType: number) => {
  584. switch (subType) {
  585. case 100000:
  586. return '口令';
  587. case 100001:
  588. return '应用内优惠券';
  589. case 300001:
  590. return '应用建议卡';
  591. case 400002:
  592. return '全搜';
  593. case 400003:
  594. return '分页符';
  595. case 500008:
  596. return '浏览器DP';
  597. case 700001:
  598. return '侧边栏';
  599. case 1000001:
  600. return '搜同款拍一拍';
  601. case 1000002:
  602. return '搜同款相册';
  603. case 1000003:
  604. return '搜同款助力栏';
  605. case 1000004:
  606. return '搜同款默认';
  607. case 1000005:
  608. return '搜同款识屏';
  609. case 1000006:
  610. return '搜同款小布CUI拍一拍';
  611. case 1000007:
  612. return '搜同款小布CUI客户端';
  613. default:
  614. return `未知(${subType})`;
  615. }
  616. };
  617. // 子场景选项,根据当前选择的type动态生成
  618. const subTypeOptions = computed(() => {
  619. const baseOptions: SelectOptionData[] = [];
  620. switch (editForm.value.type) {
  621. case 1:
  622. baseOptions.push({ label: '口令', value: 100000 });
  623. break;
  624. case 2:
  625. baseOptions.push({ label: '应用内优惠券', value: 100001 });
  626. break;
  627. case 3:
  628. baseOptions.push({ label: '应用建议卡', value: 300001 });
  629. break;
  630. case 4:
  631. baseOptions.push(
  632. { label: '全搜', value: 400002 },
  633. { label: '分页符', value: 400003 }
  634. );
  635. break;
  636. case 5:
  637. baseOptions.push({ label: '浏览器DP', value: 500008 });
  638. break;
  639. case 7:
  640. baseOptions.push({ label: '侧边栏', value: 700001 });
  641. break;
  642. case 10:
  643. baseOptions.push(
  644. { label: '搜同款拍一拍', value: 1000001 },
  645. { label: '搜同款相册', value: 1000002 },
  646. { label: '搜同款助力栏', value: 1000003 },
  647. { label: '搜同款默认', value: 1000004 },
  648. { label: '搜同款识屏', value: 1000005 },
  649. { label: '搜同款小布CUI拍一拍', value: 1000006 },
  650. { label: '搜同款小布CUI客户端', value: 1000007 }
  651. );
  652. break;
  653. default:
  654. // 默认不提供选项,允许用户自定义添加
  655. break;
  656. }
  657. return baseOptions;
  658. });
  659. const columns = computed<TableColumnData[]>(() => [
  660. {
  661. title: '#',
  662. dataIndex: 'index',
  663. slotName: 'index',
  664. width: 80,
  665. fixed: 'left',
  666. },
  667. {
  668. title: '报告日期',
  669. slotName: 'date',
  670. width: 120,
  671. },
  672. {
  673. title: '投放场景',
  674. slotName: 'type',
  675. width: 150,
  676. },
  677. {
  678. title: 'adId/adzoneId',
  679. slotName: 'adId',
  680. width: 150,
  681. },
  682. {
  683. title: '平台',
  684. slotName: 'platform',
  685. width: 200,
  686. },
  687. {
  688. title: '推送状态',
  689. slotName: 'status',
  690. width: 200,
  691. },
  692. {
  693. title: '请求量/转链数',
  694. slotName: 'request_metrics',
  695. width: 170,
  696. },
  697. {
  698. title: '曝光',
  699. slotName: 'expose_metrics',
  700. width: 170,
  701. },
  702. {
  703. title: '点击',
  704. slotName: 'click_metrics',
  705. width: 170,
  706. },
  707. {
  708. title: '唤端',
  709. slotName: 'app_metrics',
  710. width: 170,
  711. },
  712. {
  713. title: '订单',
  714. slotName: 'order_metrics',
  715. width: 170,
  716. },
  717. {
  718. title: '收益',
  719. slotName: 'income_metrics',
  720. width: 200,
  721. },
  722. {
  723. title: '时间',
  724. slotName: 'time',
  725. width: 180,
  726. },
  727. {
  728. title: '结算状态',
  729. slotName: 'settlement',
  730. width: 100,
  731. },
  732. {
  733. title: '操作',
  734. slotName: 'command',
  735. width: 240,
  736. fixed: 'right',
  737. },
  738. ]);
  739. const fetchData = async (
  740. params: PushDataReportParams = {
  741. current: 1,
  742. pageSize: 50,
  743. sort: 'report_date',
  744. order: 'descending',
  745. getTotal: true,
  746. report_date: dayjs().format('YYYY-MM-DD'),
  747. },
  748. tryPreviousDay = true
  749. ) => {
  750. setLoading(true);
  751. try {
  752. const { data } = await queryReportList(params);
  753. if (!data) return;
  754. renderData.value = data.list;
  755. pagination.current = data.page;
  756. pagination.total = data.count;
  757. // Clear selection when data is refreshed
  758. selectedRowKeys.value = [];
  759. selectedRows.value = [];
  760. // Recalculate table height after data is loaded
  761. nextTick(() => {
  762. calculateTableHeight();
  763. });
  764. if (tryPreviousDay && data.list.length === 0 && !params.type && params.report_date === dayjs().format('YYYY-MM-DD')) {
  765. const prevDay = dayjs().subtract(1, 'day').format('YYYY-MM-DD');
  766. formModel.value.report_date = prevDay;
  767. // 重置分页到第一页
  768. pagination.current = 1;
  769. fetchData({
  770. ...params,
  771. current: 1,
  772. report_date: prevDay
  773. }, false);
  774. Message.info(`当天无数据,已自动加载 ${prevDay} 的数据`);
  775. }
  776. } catch (err) {
  777. console.log(err);
  778. } finally {
  779. setLoading(false);
  780. }
  781. };
  782. const search = () => {
  783. const statusNumber = Number(formModel.value.status);
  784. const isSettlementValue = formModel.value.is_settlement;
  785. fetchData({
  786. ...basePagination,
  787. ...formModel.value,
  788. type: formModel.value.type ? Number(formModel.value.type) : undefined,
  789. platform: formModel.value.platform ? Number(formModel.value.platform) : undefined,
  790. status: statusNumber,
  791. is_settlement: isSettlementValue,
  792. getTotal: true,
  793. } as PushDataReportParams);
  794. };
  795. const onPageChange = (current: number) => {
  796. const statusNumber = Number(formModel.value.status);
  797. const isSettlementValue = Number(formModel.value.is_settlement);
  798. fetchData({
  799. ...basePagination,
  800. ...formModel.value,
  801. current,
  802. type: formModel.value.type ? Number(formModel.value.type) : undefined,
  803. platform: formModel.value.platform ? Number(formModel.value.platform) : undefined,
  804. status: statusNumber,
  805. is_settlement: isSettlementValue,
  806. getTotal: true,
  807. } as PushDataReportParams);
  808. };
  809. const visible = ref(false);
  810. const isEdit = ref(false);
  811. const editForm = ref<Partial<PushDataReportRecord>>({
  812. report_date: new Date(),
  813. is_settlement: false,
  814. type: 3,
  815. sub_type: 300001,
  816. platform: 1,
  817. distribution: 0,
  818. req_count: 0,
  819. dp_transition_succ_count: 0,
  820. transition_succ_count: 0,
  821. expose_count: 0,
  822. expose_user_count: 0,
  823. click_count: 0,
  824. click_user_count: 0,
  825. app_open_count: 0,
  826. app_open_user_count: 0,
  827. order_count: 0,
  828. order_user_count: 0,
  829. gmv: 0,
  830. income: 0,
  831. request_id: '',
  832. adId: '',
  833. adzoneId: '',
  834. });
  835. const onEdit = (current: PushDataReportRecord) => {
  836. console.log('Editing record:', current);
  837. isEdit.value = true;
  838. // 处理日期,确保它是正确的格式
  839. let formattedDate = current.report_date;
  840. if (typeof current.report_date === 'string') {
  841. formattedDate = new Date(current.report_date);
  842. }
  843. // 确保所有数值字段都是数字类型
  844. const ensureNumber = (value: any) => {
  845. return value !== undefined && value !== null ? Number(value) : 0;
  846. };
  847. editForm.value = {
  848. id: current.id,
  849. report_date: formattedDate,
  850. is_settlement: Boolean(current.is_settlement),
  851. type: ensureNumber(current.type),
  852. sub_type: ensureNumber(current.sub_type),
  853. platform: ensureNumber(current.platform || 1),
  854. platform_name: current.platform_name || getPlatformNameById(ensureNumber(current.platform || 1)),
  855. distribution: ensureNumber(current.distribution || 0),
  856. req_count: ensureNumber(current.req_count),
  857. dp_transition_succ_count: ensureNumber(current.dp_transition_succ_count),
  858. transition_succ_count: ensureNumber(current.transition_succ_count),
  859. expose_count: ensureNumber(current.expose_count),
  860. expose_user_count: ensureNumber(current.expose_user_count),
  861. click_count: ensureNumber(current.click_count),
  862. click_user_count: ensureNumber(current.click_user_count),
  863. app_open_count: ensureNumber(current.app_open_count),
  864. app_open_user_count: ensureNumber(current.app_open_user_count),
  865. order_count: ensureNumber(current.order_count),
  866. order_user_count: ensureNumber(current.order_user_count),
  867. gmv: ensureNumber(current.gmv),
  868. income: ensureNumber(current.income),
  869. request_id: current.request_id || '',
  870. status: ensureNumber(current.status),
  871. last_push_time: current.last_push_time,
  872. adId: current.adId,
  873. adzoneId: current.adzoneId,
  874. };
  875. // 确保子场景与投放场景匹配
  876. nextTick(() => {
  877. // 在DOM更新后检查子场景是否与投放场景匹配
  878. if (editForm.value.type !== undefined) {
  879. const { type } = editForm.value;
  880. // 根据类型设置对应的默认子场景值
  881. if (type === 1 && editForm.value.sub_type !== 100000) {
  882. editForm.value.sub_type = 100000;
  883. } else if (type === 3 && editForm.value.sub_type !== 300001) {
  884. editForm.value.sub_type = 300001;
  885. } else if (type === 4 && !([400002, 400003].includes(editForm.value.sub_type as number))) {
  886. editForm.value.sub_type = 400002;
  887. } else if (type === 5 && editForm.value.sub_type !== 500008) {
  888. editForm.value.sub_type = 500008;
  889. } else if (type === 7 && editForm.value.sub_type !== 700001) {
  890. editForm.value.sub_type = 700001;
  891. } else if (type === 10 && !([1000001, 1000002, 1000003, 1000004, 1000005, 1000006, 1000007].includes(editForm.value.sub_type as number))) {
  892. editForm.value.sub_type = 1000001;
  893. }
  894. }
  895. });
  896. console.log('Edit form populated:', editForm.value);
  897. visible.value = true;
  898. };
  899. const onAdd = () => {
  900. isEdit.value = false;
  901. const initialPlatform = 1; // 默认淘宝
  902. editForm.value = {
  903. report_date: new Date(),
  904. is_settlement: false,
  905. type: 3,
  906. sub_type: 300001,
  907. platform: initialPlatform,
  908. platform_name: getPlatformNameById(initialPlatform),
  909. distribution: 0,
  910. req_count: 0,
  911. dp_transition_succ_count: 0,
  912. transition_succ_count: 0,
  913. expose_count: 0,
  914. expose_user_count: 0,
  915. click_count: 0,
  916. click_user_count: 0,
  917. app_open_count: 0,
  918. app_open_user_count: 0,
  919. order_count: 0,
  920. order_user_count: 0,
  921. gmv: 0,
  922. income: 0,
  923. request_id: '',
  924. adId: '',
  925. adzoneId: '',
  926. };
  927. visible.value = true;
  928. };
  929. const onPush = async (record: PushDataReportRecord) => {
  930. try {
  931. const response = await pushReport(record.id);
  932. if (response.data.success) {
  933. Message.success(response.data.msg || '推送数据成功');
  934. // 推送成功后刷新当前数据
  935. search();
  936. } else {
  937. Message.error(response.data.msg || '推送数据失败');
  938. }
  939. } catch (error) {
  940. Message.error('请求失败');
  941. }
  942. };
  943. // 初始加载数据
  944. fetchData();
  945. const handleSelectDensity = (
  946. val: string | number | Record<string, any> | undefined,
  947. ) => {
  948. size.value = val as SizeProps;
  949. };
  950. const handleChange = (
  951. checked: boolean | (string | boolean | number)[],
  952. column: Column,
  953. index: number
  954. ) => {
  955. if (!checked) {
  956. cloneColumns.value = showColumns.value.filter(
  957. (item) => item.dataIndex !== column.dataIndex
  958. );
  959. } else {
  960. cloneColumns.value.splice(index, 0, column);
  961. }
  962. };
  963. const exchangeArray = <T extends Array<any>>(
  964. array: T,
  965. beforeIdx: number,
  966. newIdx: number,
  967. isDeep = false
  968. ): T => {
  969. const newArray = isDeep ? cloneDeep(array) : array;
  970. if (beforeIdx > -1 && newIdx > -1) {
  971. // 先替换后面的,然后拿到替换的结果替换前面的
  972. newArray.splice(
  973. beforeIdx,
  974. 1,
  975. newArray.splice(newIdx, 1, newArray[beforeIdx]).pop()
  976. );
  977. }
  978. return newArray;
  979. };
  980. const popupVisibleChange = (val: boolean) => {
  981. if (val) {
  982. nextTick(() => {
  983. const el = document.getElementById(
  984. 'tableSetting'
  985. ) as HTMLElement;
  986. const sortable = new Sortable(el, {
  987. onEnd(e: any) {
  988. const { oldIndex, newIndex } = e;
  989. exchangeArray(cloneColumns.value, oldIndex, newIndex);
  990. exchangeArray(showColumns.value, oldIndex, newIndex);
  991. },
  992. });
  993. });
  994. }
  995. };
  996. const handleSubmit = async () => {
  997. try {
  998. // 使用统一的saveReport接口
  999. const response = await saveReport(editForm.value);
  1000. if (response.data.success) {
  1001. Message.success(response.data.msg || '操作成功');
  1002. visible.value = false;
  1003. search(); // 重新加载当前筛选条件下的数据
  1004. } else {
  1005. Message.error(response.data.msg || '操作失败');
  1006. }
  1007. } catch (error) {
  1008. Message.error('请求失败');
  1009. }
  1010. };
  1011. const handleCancel = () => {
  1012. visible.value = false;
  1013. };
  1014. watch(
  1015. () => columns.value,
  1016. (val) => {
  1017. cloneColumns.value = cloneDeep(val);
  1018. cloneColumns.value.forEach((item) => {
  1019. item.checked = true;
  1020. });
  1021. showColumns.value = cloneDeep(cloneColumns.value);
  1022. },
  1023. { deep: true, immediate: true }
  1024. );
  1025. // 当切换type时,更新子场景值
  1026. watch(
  1027. () => editForm.value.type,
  1028. (newType) => {
  1029. if (newType === 1) {
  1030. editForm.value.sub_type = 100000;
  1031. } else if (newType === 2) {
  1032. editForm.value.sub_type = 100001;
  1033. } else if (newType === 3) {
  1034. editForm.value.sub_type = 300001;
  1035. } else if (newType === 4) {
  1036. editForm.value.sub_type = 400002;
  1037. } else if (newType === 7) {
  1038. editForm.value.sub_type = 700001;
  1039. } else if (newType === 10) {
  1040. editForm.value.sub_type = 1000001;
  1041. }
  1042. }
  1043. );
  1044. // 添加自动加载数据的方法
  1045. const onTypeOrDateChange = () => {
  1046. search();
  1047. };
  1048. const getStatusText = (status: number) => {
  1049. switch (status) {
  1050. case 0:
  1051. return '未推送';
  1052. case 1:
  1053. return '推送成功';
  1054. case 2:
  1055. return '推送失败';
  1056. case 3:
  1057. return '手动推送';
  1058. default:
  1059. return `未知(${status})`;
  1060. }
  1061. };
  1062. const getStatusColor = (status: number) => {
  1063. switch (status) {
  1064. case 0:
  1065. return 'gray';
  1066. case 1:
  1067. return 'green';
  1068. case 2:
  1069. return 'red';
  1070. case 3:
  1071. return 'blue';
  1072. default:
  1073. return '';
  1074. }
  1075. };
  1076. // 使用导入的platformList
  1077. const platformOptions = computed<SelectOptionData[]>(() => platformList);
  1078. // 监听platform变化,自动更新platform_name
  1079. watch(
  1080. () => editForm.value.platform,
  1081. (newPlatform) => {
  1082. if (newPlatform !== undefined) {
  1083. editForm.value.platform_name = getPlatformNameById(newPlatform);
  1084. }
  1085. }
  1086. );
  1087. // Add this before the platformOptions declaration
  1088. const filterPlatformOption = (inputValue: string, option: SelectOptionData) => {
  1089. return option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
  1090. String(option.value).includes(inputValue);
  1091. };
  1092. const clearPlatform = () => {
  1093. formModel.value.platform = '';
  1094. search(); // Trigger search after clearing
  1095. };
  1096. // Add table height calculation
  1097. const tableHeight = ref(0);
  1098. onMounted(() => {
  1099. calculateTableHeight();
  1100. window.addEventListener('resize', calculateTableHeight);
  1101. });
  1102. // Cleanup the event listener when component is unmounted
  1103. onUnmounted(() => {
  1104. window.removeEventListener('resize', calculateTableHeight);
  1105. });
  1106. const calculateTableHeight = () => {
  1107. // Get viewport height
  1108. const vh = window.innerHeight;
  1109. // Get search form height dynamically
  1110. const searchArea = document.querySelector('.general-card')?.querySelector('form')?.parentElement?.parentElement;
  1111. const searchHeight = searchArea ? searchArea.offsetHeight + 32 : 160; // 32px for margins
  1112. // Subtract header(64px), breadcrumb(40px), padding(40px), search area, table header(56px), pagination(56px)
  1113. tableHeight.value = vh - searchHeight - 380;
  1114. };
  1115. // Add after renderData declaration
  1116. const selectedRowKeys = ref<(string | number)[]>([]);
  1117. const selectedRows = ref<PushDataReportRecord[]>([]);
  1118. // 在 setup 中添加这些变量用于跟踪批量推送进度
  1119. const batchPushModalVisible = ref(false);
  1120. const batchPushProgress = ref(0);
  1121. const batchPushTotal = ref(0);
  1122. const batchPushSuccess = ref(0);
  1123. const batchPushFail = ref(0);
  1124. const batchPushModalLoading = ref(false);
  1125. // 修改的批量推送函数
  1126. const onBatchPush = async () => {
  1127. if (selectedRowKeys.value.length === 0) {
  1128. Message.warning('请选择要推送的数据');
  1129. return;
  1130. }
  1131. Modal.confirm({
  1132. title: '批量推送确认',
  1133. content: `确定要推送 ${selectedRowKeys.value.length} 条数据吗?推送过程中请勿关闭页面。`,
  1134. onOk: async () => {
  1135. // 重置进度变量
  1136. batchPushProgress.value = 0;
  1137. batchPushTotal.value = selectedRowKeys.value.length;
  1138. batchPushSuccess.value = 0;
  1139. batchPushFail.value = 0;
  1140. batchPushModalLoading.value = true;
  1141. batchPushModalVisible.value = true;
  1142. // 处理每条数据
  1143. for (let i = 0; i < selectedRowKeys.value.length; i += 1) {
  1144. const id = Number(selectedRowKeys.value[i]);
  1145. try {
  1146. // eslint-disable-next-line no-await-in-loop
  1147. const response = await pushReport(id);
  1148. if (response.data && response.data.success) {
  1149. batchPushSuccess.value += 1;
  1150. } else {
  1151. batchPushFail.value += 1;
  1152. }
  1153. } catch (error) {
  1154. batchPushFail.value += 1;
  1155. }
  1156. // 更新进度
  1157. batchPushProgress.value = i + 1;
  1158. // 使用 nextTick 确保界面刷新
  1159. // eslint-disable-next-line no-await-in-loop
  1160. await nextTick();
  1161. }
  1162. // 任务完成
  1163. batchPushModalLoading.value = false;
  1164. },
  1165. onCancel: () => {
  1166. // 用户取消操作
  1167. }
  1168. });
  1169. };
  1170. // 修改函数类型定义
  1171. const handleBatchPushComplete = (done: () => void) => {
  1172. // 清空选择并刷新数据
  1173. selectedRowKeys.value = [];
  1174. selectedRows.value = [];
  1175. search();
  1176. done(); // 关闭对话框
  1177. };
  1178. // 添加删除方法
  1179. const onDelete = (record: PushDataReportRecord) => {
  1180. Modal.confirm({
  1181. title: '确认删除',
  1182. content: `确定要删除 ID 为 ${record.id} 的数据记录吗?此操作不可恢复。`,
  1183. okText: '删除',
  1184. okButtonProps: { status: 'danger' },
  1185. cancelText: '取消',
  1186. onOk: async () => {
  1187. try {
  1188. const response = await deleteReport(record.id);
  1189. if (response.data.success) {
  1190. Message.success(response.data.msg || '删除成功');
  1191. // 刷新数据列表
  1192. search();
  1193. } else {
  1194. Message.error(response.data.msg || '删除失败');
  1195. }
  1196. } catch (error) {
  1197. Message.error('删除请求失败');
  1198. }
  1199. }
  1200. });
  1201. };
  1202. // 批量删除方法
  1203. const onBatchDelete = () => {
  1204. if (selectedRowKeys.value.length === 0) {
  1205. Message.warning('请选择要删除的数据');
  1206. return;
  1207. }
  1208. Modal.confirm({
  1209. title: '批量删除确认',
  1210. content: `确定要删除选中的 ${selectedRowKeys.value.length} 条数据吗?此操作不可恢复。`,
  1211. okText: '删除',
  1212. okButtonProps: { status: 'danger' },
  1213. cancelText: '取消',
  1214. onOk: async () => {
  1215. let successCount = 0;
  1216. let failCount = 0;
  1217. // 创建进度弹窗
  1218. const progressModal = Modal.info({
  1219. title: '删除中',
  1220. content: `0/${selectedRowKeys.value.length}条数据已处理,请稍候...`,
  1221. okButtonProps: { disabled: true },
  1222. cancelButtonProps: { disabled: true },
  1223. footer: false,
  1224. closable: false,
  1225. maskClosable: false
  1226. });
  1227. // 依次删除每条数据
  1228. for (let i = 0; i < selectedRowKeys.value.length; i += 1) {
  1229. const id = Number(selectedRowKeys.value[i]);
  1230. try {
  1231. // eslint-disable-next-line no-await-in-loop
  1232. const response = await deleteReport(id);
  1233. if (response.data && response.data.success) {
  1234. successCount += 1;
  1235. } else {
  1236. failCount += 1;
  1237. }
  1238. } catch (error) {
  1239. failCount += 1;
  1240. }
  1241. // 更新进度信息
  1242. progressModal.update({
  1243. content: `${i + 1}/${selectedRowKeys.value.length}条数据已处理,成功:${successCount},失败:${failCount},请稍候...`
  1244. });
  1245. }
  1246. // 关闭进度弹窗
  1247. progressModal.close();
  1248. // 显示结果
  1249. Modal.success({
  1250. title: '删除完成',
  1251. content: `已完成所有数据删除,成功:${successCount},失败:${failCount}`,
  1252. onOk: () => {
  1253. // 清空选择并刷新数据
  1254. selectedRowKeys.value = [];
  1255. selectedRows.value = [];
  1256. search();
  1257. }
  1258. });
  1259. }
  1260. });
  1261. };
  1262. </script>
  1263. <script lang="ts">
  1264. export default {
  1265. name: 'PushReport',
  1266. };
  1267. </script>
  1268. <style scoped lang="less">
  1269. .container {
  1270. padding: 0 20px 20px 20px;
  1271. }
  1272. :deep(.general-card) {
  1273. height: 100%;
  1274. }
  1275. .action-icon {
  1276. margin-left: 12px;
  1277. cursor: pointer;
  1278. }
  1279. .active {
  1280. color: #0960bd;
  1281. background-color: #e3f4fc;
  1282. }
  1283. .setting {
  1284. display: flex;
  1285. align-items: center;
  1286. width: 200px;
  1287. .title {
  1288. margin-left: 12px;
  1289. cursor: pointer;
  1290. }
  1291. }
  1292. .status-extra-info {
  1293. font-size: 12px;
  1294. margin-top: 4px;
  1295. max-width: 160px;
  1296. }
  1297. .text-ellipsis {
  1298. white-space: nowrap;
  1299. overflow: hidden;
  1300. text-overflow: ellipsis;
  1301. }
  1302. .text-gray {
  1303. color: #86909c;
  1304. }
  1305. .filter-with-clear {
  1306. position: relative;
  1307. width: 100%;
  1308. .clear-btn {
  1309. position: absolute;
  1310. right: 32px;
  1311. top: 50%;
  1312. transform: translateY(-50%);
  1313. color: #86909c;
  1314. z-index: 2;
  1315. height: 20px;
  1316. width: 20px;
  1317. display: flex;
  1318. align-items: center;
  1319. justify-content: center;
  1320. padding: 0;
  1321. margin-right: 4px;
  1322. &:hover {
  1323. color: #165DFF;
  1324. }
  1325. }
  1326. }
  1327. :deep(.arco-checkbox) {
  1328. z-index: 1;
  1329. pointer-events: auto !important;
  1330. }
  1331. :deep(.arco-table-th-item .arco-checkbox),
  1332. :deep(.arco-table-td .arco-checkbox) {
  1333. pointer-events: auto !important;
  1334. }
  1335. :deep(.arco-modal-body) {
  1336. font-size: 14px;
  1337. line-height: 1.5;
  1338. }
  1339. :deep(.arco-modal-title) {
  1340. font-weight: bold;
  1341. color: var(--color-text-1);
  1342. }
  1343. </style>