orders.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <template>
  2. <div class="container">
  3. <Breadcrumb :items="['menu.taobao', 'menu.taobao.orders']" />
  4. <a-card class="general-card" :title="$t('menu.taobao.orders')">
  5. <a-row>
  6. <a-col :flex="1">
  7. <a-form :model="formModel" :label-col-props="{ span: 8 }" :wrapper-col-props="{ span: 16 }"
  8. label-align="left">
  9. <a-row :gutter="24">
  10. <a-col :span="6">
  11. <a-form-item field="name" label="账号名">
  12. <selectAccount v-model="formModel.keyword" @change="changeCompany" />
  13. </a-form-item>
  14. </a-col>
  15. <a-col :span="6">
  16. <a-form-item field="tkStatus" label="订单状态">
  17. <a-select :model="formModel.tkStatus" @change="changeStatus">
  18. <a-option :value="0">所有状态</a-option>
  19. <a-option :value="3">已结算</a-option>
  20. <a-option :value="12">已付款</a-option>
  21. <a-option :value="13">已失效</a-option>
  22. <a-option :value="14">已收货</a-option>
  23. </a-select>
  24. </a-form-item>
  25. </a-col>
  26. <a-col :span="6">
  27. <a-form-item field="query_date" :label="$t('searchTable.form.order_date')">
  28. <a-range-picker v-model="formModel.query_date" style="width: 100%"
  29. @change="changeDate" />
  30. </a-form-item>
  31. </a-col>
  32. <a-col :span="6">
  33. <a-form-item field="maxTotal" label="最大记录">
  34. <a-select v-model="formModel.maxTotal">
  35. <a-option :value="1000">1000</a-option>
  36. <a-option :value="10000">10000</a-option>
  37. <a-option :value="100000">10W</a-option>
  38. <a-option :value="1000000">100W</a-option>
  39. <a-option :value="10000000">1000W</a-option>
  40. </a-select>
  41. </a-form-item>
  42. </a-col>
  43. </a-row>
  44. </a-form>
  45. </a-col>
  46. <a-divider style="height: 32px" direction="vertical" />
  47. <a-col :flex="'86px'" style="text-align: right">
  48. <a-space direction="vertical" :size="18">
  49. <a-button type="primary" @click="search">
  50. <template #icon>
  51. <icon-search />
  52. </template>
  53. {{ $t('searchTable.form.search') }}
  54. </a-button>
  55. </a-space>
  56. </a-col>
  57. </a-row>
  58. <a-divider style="margin-top: 0" />
  59. <a-row style="margin-bottom: 16px">
  60. <a-col :span="12"> </a-col>
  61. <a-col :span="12" style="
  62. display: flex;
  63. align-items: center;
  64. justify-content: end;
  65. ">
  66. <a-tooltip :content="$t('searchTable.actions.refresh')">
  67. <div class="action-icon" @click="search"><icon-refresh size="18" /></div>
  68. </a-tooltip>
  69. <a-dropdown @select="handleSelectDensity">
  70. <a-tooltip :content="$t('searchTable.actions.density')">
  71. <div class="action-icon"><icon-line-height size="18" /></div>
  72. </a-tooltip>
  73. <template #content>
  74. <a-doption v-for="item in densityList" :key="item.value" :value="item.value"
  75. :class="{ active: item.value === size }">
  76. <span>{{ item.name }}</span>
  77. </a-doption>
  78. </template>
  79. </a-dropdown>
  80. <a-tooltip :content="$t('searchTable.actions.columnSetting')">
  81. <a-popover trigger="click" position="bl" @popup-visible-change="popupVisibleChange">
  82. <div class="action-icon"><icon-settings size="18" /></div>
  83. <template #content>
  84. <div id="tableSetting">
  85. <div v-for="(item, index) in showColumns" :key="item.dataIndex" class="setting">
  86. <div style="
  87. margin-right: 4px;
  88. cursor: move;
  89. ">
  90. <icon-drag-arrow />
  91. </div>
  92. <div>
  93. <a-checkbox v-model="item.checked" @change="
  94. handleChange(
  95. $event,
  96. item as TableColumnData,
  97. index
  98. )
  99. ">
  100. </a-checkbox>
  101. </div>
  102. <div class="title">
  103. {{
  104. item.title === '#'
  105. ? '序列号'
  106. : item.title
  107. }}
  108. </div>
  109. </div>
  110. </div>
  111. </template>
  112. </a-popover>
  113. </a-tooltip>
  114. </a-col>
  115. </a-row>
  116. <a-table row-key="id" :loading="loading" :pagination="pagination"
  117. :columns="(cloneColumns as TableColumnData[])" :scroll="scroll" :scrollbar="scrollbar"
  118. :data="renderData" :bordered="false" :size="size" @page-change="onPageChange">
  119. <template #orderInfo="{ record }">
  120. <a-list-item>
  121. <a-list-item-meta>
  122. <template #avatar>
  123. <a-avatar shape="square">
  124. <img alt="avatar" :src="record.itemImg" />
  125. </a-avatar>
  126. </template>
  127. <template #title>
  128. <a target="_blank" class="link" :href="record.mktItemLink">
  129. {{ record.itemTitle }}
  130. </a>
  131. </template>
  132. <template #description>
  133. <div class="text-3">
  134. <div>店铺名:{{ record.sellerShopTitle }}
  135. <a-tooltip>
  136. <a-tag>{{ record.tradeId }}</a-tag>
  137. <template #content>
  138. <div>父订单编号:{{ record.tradeParentId }}</div>
  139. <div> 子订单编号:{{ record.tradeId }}</div>
  140. </template>
  141. </a-tooltip>
  142. </div>
  143. <div> {{ record.tbPaidTime }}付款 </div>
  144. <div v-if="record.tkEarningTime != '0001-01-01 00:00:00'">
  145. {{ record.tkEarningTime }}结算
  146. </div>
  147. </div>
  148. </template>
  149. </a-list-item-meta>
  150. </a-list-item>
  151. </template>
  152. <template #alipayTotalPrice="{ record }">
  153. <span>¥{{ record.alipayTotalPrice }}</span>
  154. </template>
  155. <template #tkTotalRateForCommissionAndSdy="{ record }">
  156. {{ record.tkTotalRateForCommissionAndSdy }}%
  157. </template>
  158. <template #pubSharePreFee="{ record }">
  159. <div class="text-3">
  160. <div>付款预估佣金收入:<span class="money-sum">¥{{ record.pubSharePreFeeForCommission }}</span></div>
  161. <div>付款预估补贴收入:<span class="money-sum">¥{{ record.pubSharePreFeeForSdy }}</span></div>
  162. </div>
  163. </template>
  164. <template #totalCommissionFee="{ record }">
  165. <div class="text-3">
  166. <div>付款预估佣金收入:<span class="money-sum">¥{{ record.pubShareFeeForCommission }}</span></div>
  167. <div>付款预估补贴收入:<span class="money-sum">¥{{ record.pubShareFeeForSdy }}</span></div>
  168. </div>
  169. </template>
  170. <template #alimamaShareFee="{ record }">
  171. <div>¥{{ record.alimamaShareFee }}</div>
  172. </template>
  173. <template #last_time="{ record }">
  174. <div class="money-sum">{{ record.last_time }}</div>
  175. </template>
  176. </a-table>
  177. </a-card>
  178. </div>
  179. </template>
  180. <script lang="ts" setup>
  181. import { useRouter } from 'vue-router';
  182. import dayjs from 'dayjs';
  183. import { computed, ref, reactive, watch, nextTick } from 'vue';
  184. import { useI18n } from 'vue-i18n';
  185. import useLoading from '@/hooks/loading';
  186. import {
  187. queryTkOrders,
  188. TkOrderRecord,
  189. TkOrdersParams,
  190. } from '@/api/taobao';
  191. import { Pagination } from '@/types/global';
  192. import type {
  193. TableData,
  194. TableColumnData,
  195. } from '@arco-design/web-vue/es/table/interface';
  196. import cloneDeep from 'lodash/cloneDeep';
  197. import Sortable from 'sortablejs';
  198. import selectAccount from './components/selectAccount.vue';
  199. type SizeProps = 'mini' | 'small' | 'medium' | 'large';
  200. type Column = TableColumnData & { checked?: true };
  201. const generateFormModel = () => {
  202. return {
  203. keyword: '',
  204. tkStatus: 0,
  205. formModel: 1000,
  206. query_date: [],
  207. current: 1,
  208. pageSize: 20,
  209. maxTotal: 1000,
  210. sort: 'tkPaidTime',
  211. order: 'descending',
  212. };
  213. };
  214. const router = useRouter();
  215. const { loading, setLoading } = useLoading(true);
  216. const { t } = useI18n();
  217. const renderData = ref<TkOrderRecord[]>([]);
  218. const formModel = ref(generateFormModel());
  219. const cloneColumns = ref<Column[]>([]);
  220. const showColumns = ref<Column[]>([]);
  221. const size = ref<SizeProps>('medium');
  222. const scrollbar = ref(true);
  223. const scroll = {
  224. x: '100%',
  225. y: '100%',
  226. maxHeight: 'calc( 100vh - 420px)',
  227. };
  228. const basePagination: Pagination = {
  229. current: 1,
  230. pageSize: 20,
  231. };
  232. const pagination = reactive({
  233. ...basePagination,
  234. });
  235. const densityList = computed(() => [
  236. {
  237. name: t('searchTable.size.mini'),
  238. value: 'mini',
  239. },
  240. {
  241. name: t('searchTable.size.small'),
  242. value: 'small',
  243. },
  244. {
  245. name: t('searchTable.size.medium'),
  246. value: 'medium',
  247. },
  248. {
  249. name: t('searchTable.size.large'),
  250. value: 'large',
  251. },
  252. ]);
  253. const columns = computed<TableColumnData[]>(() => [
  254. {
  255. title: '订单信息',
  256. dataIndex: 'xAxis',
  257. slotName: 'orderInfo',
  258. fixed: 'left',
  259. width: 450,
  260. },
  261. {
  262. title: '订单状态',
  263. dataIndex: 'tkStatusText',
  264. width: 120,
  265. },
  266. {
  267. title: '订单金额',
  268. slotName: 'alipayTotalPrice',
  269. width: 100,
  270. align: 'right'
  271. },
  272. {
  273. title: '总提成率',
  274. dataIndex: 'tkTotalRateForCommissionAndSdy',
  275. slotName: 'tkTotalRateForCommissionAndSdy',
  276. width: 120,
  277. align: 'right'
  278. },
  279. {
  280. title: '付款预估收入',
  281. dataIndex: 'pubSharePreFee',
  282. slotName: 'pubSharePreFee',
  283. width: 220,
  284. align: 'right'
  285. },
  286. {
  287. title: '结算预估收入',
  288. dataIndex: 'totalCommissionFee',
  289. slotName: 'totalCommissionFee',
  290. width: 220,
  291. align: 'right'
  292. },
  293. {
  294. title: '平台技术服务费',
  295. dataIndex: 'alimamaShareFee',
  296. slotName: 'alimamaShareFee',
  297. width: 140,
  298. align: 'right'
  299. },
  300. {
  301. title: '最后更新时间',
  302. dataIndex: 'last_time',
  303. slotName: 'last_time',
  304. width: 100,
  305. align: 'right'
  306. },
  307. ]);
  308. // renderData.value.tkStatusText
  309. const fetchData = async (
  310. params: TkOrdersParams = {
  311. current: 1,
  312. pageSize: 20,
  313. maxTotal: 1000,
  314. sort: 'tkPaidTime',
  315. order: 'descending',
  316. }
  317. ) => {
  318. setLoading(true);
  319. try {
  320. const { data } = await queryTkOrders(params);
  321. console.log(data);
  322. if (!data) return;
  323. renderData.value = data.list;
  324. pagination.current = data.page;
  325. pagination.total = data.count;
  326. console.log(data);
  327. } catch (err) {
  328. console.log(err);
  329. } finally {
  330. setLoading(false);
  331. }
  332. };
  333. const search = () => {
  334. fetchData({
  335. ...basePagination,
  336. ...formModel.value,
  337. } as unknown as TkOrdersParams);
  338. };
  339. const onPageChange = (current: number) => {
  340. fetchData({
  341. ...basePagination,
  342. ...formModel.value,
  343. current
  344. });
  345. };
  346. fetchData();
  347. const reset = () => {
  348. formModel.value = generateFormModel();
  349. };
  350. const changeCompany = (value: any) => {
  351. const reset = () => {
  352. formModel.value = generateFormModel();
  353. };
  354. formModel.value.keyword = value
  355. fetchData({
  356. ...basePagination,
  357. ...formModel.value,
  358. } as unknown as TkOrdersParams);
  359. };
  360. const changeStatus = (value: any) => {
  361. const reset = () => {
  362. formModel.value = generateFormModel();
  363. };
  364. console.log(value)
  365. formModel.value.tkStatus = value
  366. fetchData({
  367. ...basePagination,
  368. ...formModel.value,
  369. } as unknown as TkOrdersParams);
  370. };
  371. const changeDate = (value: any) => {
  372. const reset = () => {
  373. formModel.value = generateFormModel();
  374. };
  375. formModel.value.query_date = value
  376. fetchData({
  377. ...basePagination,
  378. ...formModel.value,
  379. } as unknown as TkOrdersParams);
  380. };
  381. const handleSelectDensity = (
  382. val: string | number | Record<string, any> | undefined,
  383. e: Event
  384. ) => {
  385. size.value = val as SizeProps;
  386. };
  387. const handleChange = (
  388. checked: boolean | (string | boolean | number)[],
  389. column: Column,
  390. index: number
  391. ) => {
  392. if (!checked) {
  393. cloneColumns.value = showColumns.value.filter(
  394. (item) => item.dataIndex !== column.dataIndex
  395. );
  396. } else {
  397. cloneColumns.value.splice(index, 0, column);
  398. }
  399. };
  400. const exchangeArray = <T extends Array<any>>(
  401. array: T,
  402. beforeIdx: number,
  403. newIdx: number,
  404. isDeep = false
  405. ): T => {
  406. const newArray = isDeep ? cloneDeep(array) : array;
  407. if (beforeIdx > -1 && newIdx > -1) {
  408. // 先替换后面的,然后拿到替换的结果替换前面的
  409. newArray.splice(
  410. beforeIdx,
  411. 1,
  412. newArray.splice(newIdx, 1, newArray[beforeIdx]).pop()
  413. );
  414. }
  415. return newArray;
  416. };
  417. const popupVisibleChange = (val: boolean) => {
  418. if (val) {
  419. nextTick(() => {
  420. const el = document.getElementById(
  421. 'tableSetting'
  422. ) as HTMLElement;
  423. const sortable = new Sortable(el, {
  424. onEnd(e: any) {
  425. const { oldIndex, newIndex } = e;
  426. exchangeArray(cloneColumns.value, oldIndex, newIndex);
  427. exchangeArray(showColumns.value, oldIndex, newIndex);
  428. },
  429. });
  430. });
  431. }
  432. };
  433. watch(
  434. () => columns.value,
  435. (val) => {
  436. cloneColumns.value = cloneDeep(val);
  437. cloneColumns.value.forEach((item, index) => {
  438. item.checked = true;
  439. });
  440. showColumns.value = cloneDeep(cloneColumns.value);
  441. },
  442. { deep: true, immediate: true }
  443. );
  444. </script>
  445. <script lang="ts">
  446. export default {
  447. name: 'tkOrders',
  448. };
  449. </script>
  450. <style scoped lang="less">
  451. .container {
  452. padding: 0 20px 20px 20px;
  453. }
  454. :deep(.arco-table-th) {
  455. &:last-child {
  456. .arco-table-th-item-title {
  457. margin-left: 16px;
  458. }
  459. }
  460. }
  461. .action-icon {
  462. margin-left: 12px;
  463. cursor: pointer;
  464. }
  465. .active {
  466. color: #0960bd;
  467. background-color: #e3f4fc;
  468. }
  469. .setting {
  470. display: flex;
  471. align-items: center;
  472. width: 200px;
  473. .title {
  474. margin-left: 12px;
  475. cursor: pointer;
  476. }
  477. }
  478. .arco-table-td,
  479. .arco-table-td div,
  480. .arco-table-td span {
  481. font-size: 12px !important;
  482. }
  483. .text-3 {
  484. color: var(--color-text-3);
  485. }
  486. .money-sum {
  487. color: var(--color-text-2) !important;
  488. }
  489. .link {
  490. color: var(--color-text-t);
  491. text-decoration: none;
  492. }
  493. .link:hover {
  494. color: blue;
  495. text-decoration: underline;
  496. }
  497. </style>