bill_dailys.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['menu.taobao', 'menu.taobao.bill_dailys']" />
  4. <a-card class="general-card" :title="$t('menu.taobao.bill_dailys')">
  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="8">
  11. <a-form-item field="keyword" label="账号名">
  12. <!-- <a-input v-model="formModel.keyword" placeholder="指定账号名" /> -->
  13. <a-select :model="formModel.keyword" placeholder="Please select ..."
  14. @change="changeCompany">
  15. <a-option value="">所有账号</a-option>
  16. <a-option :key="item.id" v-for="item of accounts" :value="item.company"
  17. :label="item.company" />
  18. </a-select>
  19. </a-form-item>
  20. </a-col>
  21. <a-col :span="8">
  22. <a-form-item field="query_date" :label="$t('searchTable.form.report_date')">
  23. <a-range-picker v-model="formModel.query_date" style="width: 100%"
  24. @change="search" />
  25. </a-form-item>
  26. </a-col>
  27. </a-row>
  28. </a-form>
  29. </a-col>
  30. <a-divider style="height: 32px" direction="vertical" />
  31. <a-col :flex="'86px'" style="text-align: right">
  32. <a-space direction="vertical" :size="18">
  33. <a-button type="primary" @click="search">
  34. <template #icon>
  35. <icon-search />
  36. </template>
  37. {{ $t('searchTable.form.search') }}
  38. </a-button>
  39. </a-space>
  40. </a-col>
  41. </a-row>
  42. <a-divider style="margin-top: 0" />
  43. <a-row style="margin-bottom: 16px">
  44. <a-col :span="12"> </a-col>
  45. <a-col :span="12" style="
  46. display: flex;
  47. align-items: center;
  48. justify-content: end;
  49. ">
  50. <a-tooltip :content="$t('searchTable.actions.refresh')">
  51. <div class="action-icon" @click="search"><icon-refresh size="18" /></div>
  52. </a-tooltip>
  53. <a-dropdown @select="handleSelectDensity">
  54. <a-tooltip :content="$t('searchTable.actions.density')">
  55. <div class="action-icon"><icon-line-height size="18" /></div>
  56. </a-tooltip>
  57. <template #content>
  58. <a-doption v-for="item in densityList" :key="item.value" :value="item.value"
  59. :class="{ active: item.value === size }">
  60. <span>{{ item.name }}</span>
  61. </a-doption>
  62. </template>
  63. </a-dropdown>
  64. <a-tooltip :content="$t('searchTable.actions.columnSetting')">
  65. <a-popover trigger="click" position="bl" @popup-visible-change="popupVisibleChange">
  66. <div class="action-icon"><icon-settings size="18" /></div>
  67. <template #content>
  68. <div id="tableSetting">
  69. <div v-for="(item, index) in showColumns" :key="item.dataIndex" class="setting">
  70. <div style="
  71. margin-right: 4px;
  72. cursor: move;
  73. ">
  74. <icon-drag-arrow />
  75. </div>
  76. <div>
  77. <a-checkbox v-model="item.checked" @change="
  78. handleChange(
  79. $event,
  80. item as TableColumnData,
  81. index
  82. )
  83. ">
  84. </a-checkbox>
  85. </div>
  86. <div class="title">
  87. {{
  88. item.title === '#'
  89. ? '序列号'
  90. : item.title
  91. }}
  92. </div>
  93. </div>
  94. </div>
  95. </template>
  96. </a-popover>
  97. </a-tooltip>
  98. </a-col>
  99. </a-row>
  100. <a-table row-key="id" :loading="loading" :pagination="pagination" :summary="true" summary-text="汇总"
  101. :columns="(cloneColumns as TableColumnData[])" :scroll="scroll" :scrollbar="scrollbar"
  102. :data="renderData" :bordered="false" :size="size" :load-more="loadMore" @page-change="onPageChange">
  103. <template #xAxis="{ record }">
  104. {{ dayjs(record.belongTime).format('YYYY-MM-DD') }}
  105. </template>
  106. <template #summary-cell="{ column, record }">
  107. <div>{{ toAmount(record[column.dataIndex]) }}</div>
  108. </template>
  109. <template #total_count="{ record }">
  110. <a-popover v-if="record.total_count > 0" popup-container="body" trigger="click">
  111. <a-tag color="green">{{ record.total_count }}</a-tag>
  112. <template #content>
  113. <CategoriesPercent :record="record" />
  114. </template>
  115. </a-popover>
  116. </template>
  117. </a-table>
  118. </a-card>
  119. </div>
  120. </template>
  121. <script lang="ts" setup>
  122. import { useRouter } from 'vue-router';
  123. import dayjs from 'dayjs';
  124. import { computed, ref, reactive, watch, nextTick } from 'vue';
  125. import { useI18n } from 'vue-i18n';
  126. import useLoading from '@/hooks/loading';
  127. import {
  128. TkSettleBillRecord,
  129. TkSettleBillsParams,
  130. queryTkSettleBills,
  131. queryTkSettleBillsTotal,
  132. TkPoolRecord,
  133. TkPoolParams,
  134. queryTkPool,
  135. TkSettleBillsRes
  136. } from '@/api/taobao';
  137. import { Pagination } from '@/types/global';
  138. import type {
  139. TableData,
  140. TableColumnData,
  141. } from '@arco-design/web-vue/es/table/interface';
  142. import cloneDeep from 'lodash/cloneDeep';
  143. import Sortable from 'sortablejs';
  144. import CategoriesPercent from './components/categories-percent.vue';
  145. type SizeProps = 'mini' | 'small' | 'medium' | 'large';
  146. type Column = TableColumnData & { checked?: true };
  147. const generateFormModel = () => {
  148. let keyword = ''
  149. if (router.currentRoute.value.query.name) {
  150. keyword = router.currentRoute.value.query.name.toString()
  151. }
  152. return {
  153. keyword,
  154. query_date: [],
  155. current: 1,
  156. pageSize: 999,
  157. sort: 'belongTime',
  158. order: 'descending',
  159. };
  160. };
  161. const router = useRouter();
  162. const { loading, setLoading } = useLoading(true);
  163. const { t } = useI18n();
  164. const renderData = ref<TkSettleBillRecord[]>([]);
  165. const formModel = ref(generateFormModel());
  166. const cloneColumns = ref<Column[]>([]);
  167. const showColumns = ref<Column[]>([]);
  168. const accounts = ref<TkPoolRecord[]>([]);
  169. const size = ref<SizeProps>('medium');
  170. const scrollbar = ref(true);
  171. const scroll = {
  172. x: '100%',
  173. y: '100%',
  174. maxHeight: 'calc( 100vh - 420px)',
  175. };
  176. const basePagination: Pagination = {
  177. current: 1,
  178. pageSize: 100,
  179. };
  180. const pagination = reactive({
  181. ...basePagination,
  182. });
  183. const densityList = computed(() => [
  184. {
  185. name: t('searchTable.size.mini'),
  186. value: 'mini',
  187. },
  188. {
  189. name: t('searchTable.size.small'),
  190. value: 'small',
  191. },
  192. {
  193. name: t('searchTable.size.medium'),
  194. value: 'medium',
  195. },
  196. {
  197. name: t('searchTable.size.large'),
  198. value: 'large',
  199. },
  200. ]);
  201. const columns = computed<TableColumnData[]>(() => [
  202. {
  203. title: '日期',
  204. dataIndex: 'xAxis',
  205. slotName: 'xAxis',
  206. fixed: 'left',
  207. width: 150,
  208. },
  209. {
  210. title: '所属账号',
  211. dataIndex: 'accountName',
  212. width: 150,
  213. },
  214. {
  215. title: '结算总收入',
  216. dataIndex: 'settleIncomeTotal',
  217. slotName: 'settleIncomeTotal',
  218. width: 140,
  219. align: "right",
  220. },
  221. {
  222. title: '他方分成',
  223. dataIndex: 'dividedIncome',
  224. slotName: 'dividedIncome',
  225. width: 140,
  226. align: "right",
  227. },
  228. {
  229. title: '平台服务费',
  230. dataIndex: 'platformServiceFee',
  231. slotName: 'platformServiceFee',
  232. width: 140,
  233. align: "right",
  234. },
  235. {
  236. title: '预计收益',
  237. dataIndex: 'expectedEarnings',
  238. slotName: 'expectedEarnings',
  239. width: 140,
  240. align: "right",
  241. },
  242. {
  243. title: '状态',
  244. dataIndex: 'settleStatus',
  245. width: 120,
  246. },
  247. {
  248. title: '业务类型',
  249. dataIndex: 'bizTypeDesc',
  250. width: 120,
  251. },
  252. {
  253. title: '收益类型',
  254. dataIndex: 'incomeTypeDesc',
  255. width: 120,
  256. },
  257. {
  258. title: '最后更新时间',
  259. dataIndex: 'last_time',
  260. width: 180,
  261. },
  262. ]);
  263. const toAmount = (val: any): any => {
  264. console.log(val);
  265. if (typeof val === 'number' && !Number.isNaN(val)) {
  266. return parseFloat(val.toFixed(2));
  267. }
  268. return val;
  269. }
  270. const loadAccounts = async (
  271. params: TkPoolParams = {
  272. current: 1,
  273. pageSize: 9999,
  274. sort: 'status',
  275. order: 'descending',
  276. getTotal: false,
  277. }
  278. ) => {
  279. setLoading(true);
  280. try {
  281. const { data } = await queryTkPool(params);
  282. console.log(data);
  283. if (!data) return;
  284. // const { data } = await queryTkPool(params);
  285. accounts.value = data.list;
  286. } catch (err) {
  287. console.log(err);
  288. // you can report use errorHandler or other
  289. } finally {
  290. setLoading(false);
  291. }
  292. };
  293. const fetchData = async (
  294. params: TkSettleBillsParams = {
  295. current: 1,
  296. pageSize: 100,
  297. sort: 'belongTime',
  298. order: 'descending',
  299. }
  300. ) => {
  301. setLoading(true);
  302. try {
  303. console.log(params)
  304. if (params.keyword) {
  305. const { data } = await queryTkSettleBills(params);
  306. if (!data) return;
  307. const list = data.list.map((item: TkSettleBillRecord) => ({
  308. ...item,
  309. isLeaf: true,
  310. }));
  311. renderData.value = list;
  312. pagination.current = data.page;
  313. pagination.total = data.count;
  314. } else {
  315. const { data } = await queryTkSettleBillsTotal(params);
  316. if (!data) return;
  317. renderData.value = data.list;
  318. pagination.current = data.page;
  319. pagination.total = data.count;
  320. }
  321. } catch (err) {
  322. console.log(err);
  323. // you can report use errorHandler or other
  324. } finally {
  325. setLoading(false);
  326. }
  327. };
  328. const loadMore = async (
  329. row: TableData,
  330. done: (data: TableData[]) => void
  331. ) => {
  332. setLoading(true);
  333. try {
  334. let list: TableData[] = [];
  335. if (row.accountName === '') {
  336. console.log(row.belongTime)
  337. const params: TkSettleBillsParams = {
  338. query_date: [row.belongTime, row.belongTime],
  339. current: 1,
  340. pageSize: 999,
  341. sort: 'belongTime',
  342. order: 'descending',
  343. };
  344. const { data } = await queryTkSettleBills(params);
  345. console.log(data);
  346. if (!data) return;
  347. list = data.list.map((item: TkSettleBillRecord) => ({
  348. ...item,
  349. isLeaf: true,
  350. }));
  351. console.log(list)
  352. }
  353. done(list);
  354. } catch (err) {
  355. console.log(err);
  356. // you can report use errorHandler or other
  357. } finally {
  358. setLoading(false);
  359. }
  360. };
  361. const search = () => {
  362. fetchData({
  363. ...basePagination,
  364. ...formModel.value,
  365. } as unknown as TkSettleBillsParams);
  366. };
  367. const changeCompany = (value: any) => {
  368. const reset = () => {
  369. formModel.value = generateFormModel();
  370. };
  371. formModel.value.keyword = value
  372. fetchData({
  373. ...basePagination,
  374. ...formModel.value,
  375. } as unknown as TkSettleBillsParams);
  376. };
  377. const onPageChange = (current: number) => {
  378. fetchData({ ...basePagination, current });
  379. };
  380. loadAccounts()
  381. fetchData({ ...formModel.value } as unknown as TkSettleBillsParams);
  382. const reset = () => {
  383. formModel.value = generateFormModel();
  384. };
  385. const handleSelectDensity = (
  386. val: string | number | Record<string, any> | undefined,
  387. e: Event
  388. ) => {
  389. size.value = val as SizeProps;
  390. };
  391. const handleChange = (
  392. checked: boolean | (string | boolean | number)[],
  393. column: Column,
  394. index: number
  395. ) => {
  396. if (!checked) {
  397. cloneColumns.value = showColumns.value.filter(
  398. (item) => item.dataIndex !== column.dataIndex
  399. );
  400. } else {
  401. cloneColumns.value.splice(index, 0, column);
  402. }
  403. };
  404. const exchangeArray = <T extends Array<any>>(
  405. array: T,
  406. beforeIdx: number,
  407. newIdx: number,
  408. isDeep = false
  409. ): T => {
  410. const newArray = isDeep ? cloneDeep(array) : array;
  411. if (beforeIdx > -1 && newIdx > -1) {
  412. // 先替换后面的,然后拿到替换的结果替换前面的
  413. newArray.splice(
  414. beforeIdx,
  415. 1,
  416. newArray.splice(newIdx, 1, newArray[beforeIdx]).pop()
  417. );
  418. }
  419. return newArray;
  420. };
  421. const popupVisibleChange = (val: boolean) => {
  422. if (val) {
  423. nextTick(() => {
  424. const el = document.getElementById(
  425. 'tableSetting'
  426. ) as HTMLElement;
  427. const sortable = new Sortable(el, {
  428. onEnd(e: any) {
  429. const { oldIndex, newIndex } = e;
  430. exchangeArray(cloneColumns.value, oldIndex, newIndex);
  431. exchangeArray(showColumns.value, oldIndex, newIndex);
  432. },
  433. });
  434. });
  435. }
  436. };
  437. watch(
  438. () => columns.value,
  439. (val) => {
  440. cloneColumns.value = cloneDeep(val);
  441. cloneColumns.value.forEach((item, index) => {
  442. item.checked = true;
  443. });
  444. showColumns.value = cloneDeep(cloneColumns.value);
  445. },
  446. { deep: true, immediate: true }
  447. );
  448. </script>
  449. <script lang="ts">
  450. export default {
  451. name: 'bill_dailys',
  452. };
  453. </script>
  454. <style scoped lang="less">
  455. .container {
  456. padding: 0 20px 20px 20px;
  457. }
  458. :deep(.arco-table-th) {
  459. &:last-child {
  460. .arco-table-th-item-title {
  461. margin-left: 16px;
  462. }
  463. }
  464. }
  465. .action-icon {
  466. margin-left: 12px;
  467. cursor: pointer;
  468. }
  469. .active {
  470. color: #0960bd;
  471. background-color: #e3f4fc;
  472. }
  473. .setting {
  474. display: flex;
  475. align-items: center;
  476. width: 200px;
  477. .title {
  478. margin-left: 12px;
  479. cursor: pointer;
  480. }
  481. }
  482. </style>