index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. <script setup lang="tsx">
  2. import { onMounted, reactive, ref, unref, useTemplateRef, watch } from 'vue';
  3. import { useRouter } from 'vue-router';
  4. import { NTag, useDialog } from 'naive-ui';
  5. import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
  6. import {
  7. fetchBreakDownload,
  8. fetchExportList,
  9. fetchExportOrderList,
  10. fetchGetDeliveryOrderList,
  11. fetchGetDeliveryStatusNum
  12. } from '@/service/api/order-manage/normal-order';
  13. // import { fetchGetLoginUserList } from '@/service/api/common';
  14. import { useAppStore } from '@/store/modules/app';
  15. import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
  16. import { useAuth } from '@/hooks/business/auth';
  17. // import { copyTextToClipboard } from '@/utils/zt';
  18. import { commonExport } from '@/utils/common';
  19. import { $t } from '@/locales';
  20. import { useForm } from '@/components/zt/Form/hooks/useForm';
  21. import { useModal } from '@/components/zt/Modal/hooks/useModal';
  22. import { useTable } from '@/components/zt/Table/hooks/useTable';
  23. // import { type } from '../../../../packages/axios/src/index';
  24. import { SearchForm, orderStatus } from './normal-order';
  25. import DeliveryModal from './component/delivery-modal.vue';
  26. import NormalMoadl from './component/normal-modal.vue';
  27. const router = useRouter();
  28. const appStore = useAppStore();
  29. const checkedRowKeys = ref([]);
  30. const activeTab = ref('all');
  31. const statusList = ref<{ label: string; value: string; num?: number }[]>([]);
  32. const orderMoadl = useTemplateRef('orderMoadl');
  33. // const ShipmentModal = useTemplateRef('Shipment');
  34. const channelIdList = ref([]);
  35. const searchForm = ref();
  36. const searchParams = reactive({
  37. pageNum: 1,
  38. pageSize: 10
  39. });
  40. const [registerSearchForm, { getFieldsValue, setFieldsValue }] = useForm({
  41. schemas: [
  42. // {
  43. // field: 'channelIdList',
  44. // label: '所属企业',
  45. // component: 'ApiSelect',
  46. // componentProps: {
  47. // api: () => fetchGetLoginUserList(),
  48. // labelFeild: 'channelName',
  49. // valueFeild: 'id',
  50. // multiple: true,
  51. // onUpdateValue: () => {
  52. // nextTick(() => {
  53. // handleSearch();
  54. // });
  55. // },
  56. // getOptions: async (options: any) => {
  57. // await setFieldsValue({ channelIdList: [options[0].id] });
  58. // handleSearch();
  59. // }
  60. // }
  61. // },
  62. ...SearchForm
  63. ],
  64. showAdvancedButton: false,
  65. labelWidth: 120,
  66. layout: 'horizontal',
  67. size: 'small',
  68. gridProps: {
  69. cols: '1 xl:4 s:1 l:3',
  70. itemResponsive: true
  71. },
  72. collapsedRows: 1
  73. });
  74. const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedTable({
  75. api: () => fetchGetDeliveryOrderList({ ...searchParams, orderStatus: activeTab.value, ...unref(searchForm) }),
  76. transform: response => defaultTransform(response),
  77. immediate: false,
  78. paginationProps: {
  79. pageSizes: import.meta.env.VITE_PAGE_SIZE.split(',').map(Number)
  80. },
  81. onPaginationParamsChange: params => {
  82. searchParams.pageNum = Number(params.page);
  83. searchParams.pageSize = Number(params.pageSize);
  84. },
  85. columns: () => [
  86. {
  87. key: 'orderNumber',
  88. title: '订单编号',
  89. align: 'center',
  90. width: 220
  91. },
  92. {
  93. key: 'consigneeAddress',
  94. title: '业务类型',
  95. align: 'center',
  96. width: 120,
  97. render: row => {
  98. return <NTag class={'mt7'}>{row.businessType}</NTag>;
  99. }
  100. },
  101. {
  102. key: 'info',
  103. title: '客户信息',
  104. align: 'center',
  105. width: 220,
  106. render: row => {
  107. return (
  108. <div class={'mt7'}>
  109. <div>
  110. {row.consigneeName}
  111. {row.consigneeMobile}
  112. </div>
  113. <div>{row.consigneeAddress}</div>
  114. </div>
  115. );
  116. }
  117. },
  118. {
  119. key: 'status',
  120. title: '订单状态',
  121. align: 'center',
  122. width: 120,
  123. render: row => {
  124. const statusKey = row.hbOrderStatus as keyof typeof orderStatus;
  125. const statusText = orderStatus[statusKey] || '未知状态';
  126. return <NTag class={'mt7'}>{statusText}</NTag>;
  127. }
  128. },
  129. {
  130. key: 'createTime',
  131. title: '下单时间',
  132. align: 'center',
  133. width: 180,
  134. render: row => {
  135. return <div>{row.createTime?.replace('T', ' ')}</div>;
  136. }
  137. },
  138. {
  139. key: 'operate',
  140. title: $t('common.operate'),
  141. align: 'center',
  142. width: 150,
  143. fixed: 'right',
  144. render: row => {
  145. return (
  146. <div class={'mt7'}>
  147. <n-button size="small" type="primary" ghost onClick={() => handleOrderDetail(row)}>
  148. 订单详情
  149. </n-button>
  150. </div>
  151. );
  152. }
  153. }
  154. ]
  155. });
  156. const [registerTable, { refresh, setTableLoading }] = useTable({
  157. tableConfig: {
  158. keyField: 'id',
  159. title: '',
  160. showAddButton: false,
  161. showTableHeaderAction: false,
  162. showSearch: false,
  163. minHeight: 400
  164. }
  165. });
  166. const exportColumns: NaiveUI.TableColumn<InternalRowData>[] = [
  167. {
  168. key: 'index',
  169. title: '序号',
  170. align: 'center',
  171. width: 100,
  172. render(_, rowIndex) {
  173. return rowIndex + 1;
  174. }
  175. },
  176. {
  177. key: 'taskName',
  178. title: '任务名称',
  179. align: 'center',
  180. minWidth: 100
  181. },
  182. {
  183. key: 'updateTime',
  184. title: '时间',
  185. align: 'center',
  186. minWidth: 100,
  187. render: row => {
  188. return (
  189. <div>
  190. <div>创建时间:{row.createTime}</div>
  191. <div>完成时间:{row.updateTime}</div>
  192. </div>
  193. );
  194. }
  195. },
  196. {
  197. key: 'operator',
  198. title: '操作人',
  199. align: 'center',
  200. minWidth: 100
  201. },
  202. {
  203. key: 'exportStatus',
  204. title: '状态',
  205. align: 'center',
  206. minWidth: 100,
  207. render: row => {
  208. if (row.exportStatus == 0) {
  209. return (
  210. <div>
  211. 请耐心等待,正在导出中...
  212. <n-button text type="info" onClick={() => handleBreak(row.id as string)}>
  213. 中断
  214. </n-button>
  215. </div>
  216. );
  217. } else if (row.exportStatus == 1) {
  218. return (
  219. <div>
  220. 未下载
  221. <n-button text type="info" onClick={() => handleDownload(row.id as string)}>
  222. 下载
  223. </n-button>
  224. </div>
  225. );
  226. } else if (row.exportStatus == 2) {
  227. return <div class={'text-gray-500'}>生成文件失败</div>;
  228. } else if (row.exportStatus == 3) {
  229. return (
  230. <div>
  231. 已下载&nbsp;
  232. <n-button text type="info" onClick={() => handleDownload(row.id as string)}>
  233. 下载
  234. </n-button>
  235. </div>
  236. );
  237. } else if (row.exportStatus == 4) {
  238. return <div>导出失败</div>;
  239. }
  240. return <div>进行中</div>;
  241. }
  242. }
  243. ];
  244. async function handleDownload(id: string) {
  245. setTableLoading(true);
  246. await commonExport('/platform/exportTask/download', { fileId: id }, '正常订单列表.xlsx');
  247. refresh();
  248. }
  249. async function handleBreak(id: string) {
  250. setTableLoading(true);
  251. await fetchBreakDownload(id);
  252. refresh();
  253. }
  254. const [registerModalPrice, { openModal }] = useModal({
  255. title: '导出记录',
  256. width: 1200,
  257. height: 600,
  258. showFooter: false
  259. });
  260. const dialog = useDialog();
  261. // async function handleDeivery(row: Api.delivery.deliveryOrder) {
  262. // if (!row.orderNumber) {
  263. // window.$message?.error('订单异常');
  264. // return;
  265. // }
  266. // ShipmentModal.value?.handleOpenOrder(row.orderNumber);
  267. // }
  268. function handleOrderDetail(row: Api.delivery.deliveryOrder) {
  269. if (!row.orderNumber) {
  270. window.$message?.error('订单异常');
  271. }
  272. router.push({
  273. path: '/order-manage/order-detail',
  274. query: {
  275. orderNumber: row.orderNumber
  276. }
  277. });
  278. // orderMoadl.value?.open(String(row.orderNumber));
  279. }
  280. async function getNums() {
  281. const form = getFieldsValue();
  282. const params = {
  283. ...form,
  284. channelIdList: channelIdList.value,
  285. startTime: form.createTime ? form.createTime[0] : null,
  286. endTime: form.createTime ? form.createTime[1] : null,
  287. createTime: null
  288. };
  289. const { data: keyData } = await fetchGetDeliveryStatusNum(params);
  290. if (!keyData) return;
  291. const orderStatusList = [
  292. {
  293. label: '全部',
  294. value: 'all'
  295. },
  296. {
  297. label: '待支付',
  298. value: 'paddingPay'
  299. },
  300. {
  301. label: '进行中',
  302. value: 'ing'
  303. },
  304. {
  305. label: '已完成',
  306. value: 'completed'
  307. },
  308. {
  309. label: '已取消',
  310. value: 'cancel'
  311. }
  312. ];
  313. const updatedOrderStatusList = orderStatusList.map(item => {
  314. const key = item.value as keyof typeof keyData;
  315. if (Object.hasOwn(keyData, key)) {
  316. return {
  317. ...item,
  318. num: keyData[key]
  319. };
  320. }
  321. return item;
  322. });
  323. // console.log(updatedOrderStatusList, 'updatedOrderStatusList');
  324. statusList.value = updatedOrderStatusList;
  325. }
  326. watch(
  327. () => [activeTab.value],
  328. () => {
  329. searchParams.pageNum = 1;
  330. getData();
  331. }
  332. );
  333. function handleSearch() {
  334. const form = getFieldsValue();
  335. if (form.createTime) {
  336. form.startTime = form.createTime[0];
  337. form.endTime = form.createTime[1];
  338. delete form.createTime;
  339. }
  340. channelIdList.value = form.channelIdList;
  341. searchForm.value = form;
  342. getData();
  343. getNums();
  344. }
  345. onMounted(() => {
  346. getData();
  347. getNums();
  348. });
  349. function handleReset() {
  350. searchForm.value = getFieldsValue();
  351. searchForm.value.channelIdList = channelIdList.value;
  352. setFieldsValue({ channelIdList: channelIdList.value });
  353. getData();
  354. getNums();
  355. }
  356. // async function handleCopy(row: Api.delivery.deliveryOrder, key: string) {
  357. // if (!row[key]) {
  358. // window.$message?.error('订单编号不存在');
  359. // return;
  360. // }
  361. // await copyTextToClipboard(row[key]);
  362. // }
  363. function handleRefsh() {
  364. getData();
  365. getNums();
  366. }
  367. async function handleExport() {
  368. loading.value = true;
  369. try {
  370. // await commonExport(
  371. // '/platform/order/export',
  372. // { ...getFieldsValue(), orderStatus: activeTab.value },
  373. // '正常订单列表.xlsx'
  374. // );
  375. const newParams = getFieldsValue();
  376. if (newParams.createTime) {
  377. newParams.startTime = newParams.createTime[0];
  378. newParams.endTime = newParams.createTime[1];
  379. delete newParams.createTime;
  380. }
  381. await fetchExportOrderList({ ...newParams, orderStatus: activeTab.value });
  382. dialog.success({
  383. title: '提示',
  384. content: () => {
  385. return (
  386. <div>
  387. <p>导出操作进行中......</p>
  388. <p>是否进入导出记录</p>
  389. </div>
  390. );
  391. },
  392. positiveText: '确定',
  393. negativeText: '取消',
  394. onPositiveClick: () => {
  395. openModal();
  396. },
  397. onNegativeClick: () => {}
  398. });
  399. } finally {
  400. loading.value = false;
  401. }
  402. }
  403. async function handleExportLog() {
  404. loading.value = true;
  405. try {
  406. openModal();
  407. } finally {
  408. loading.value = false;
  409. }
  410. }
  411. </script>
  412. <template>
  413. <div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
  414. <NCard :bordered="false" size="small">
  415. <NCollapse display-directive="show" default-expanded-names="search">
  416. <NCollapseItem title="搜索" name="search">
  417. <BasicForm @register-form="registerSearchForm" @submit="handleSearch" @reset="handleReset" />
  418. </NCollapseItem>
  419. </NCollapse>
  420. </NCard>
  421. <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
  422. <template #header>
  423. <div class="mr3">订单列表</div>
  424. <NScrollbar x-scrollable>
  425. <div class="flex items-center">
  426. <div class="max-w-800px">
  427. <NTabs v-model:value="activeTab" type="line" animated display-directive="show">
  428. <NTab
  429. v-for="item in statusList"
  430. :key="item.value"
  431. :name="item.value"
  432. :tab="`${item.label}(${item.num})`"
  433. ></NTab>
  434. </NTabs>
  435. </div>
  436. </div>
  437. </NScrollbar>
  438. </template>
  439. <template #header-extra>
  440. <NButton
  441. v-if="useAuth().hasAuth('order:user:export')"
  442. size="small"
  443. type="primary"
  444. class="ml20px mt30px"
  445. ghost
  446. :loading="loading"
  447. :disabled="data.length == 0"
  448. @click="handleExport"
  449. >
  450. <template #icon>
  451. <SvgIcon icon="mingcute:file-export-line"></SvgIcon>
  452. </template>
  453. 导出
  454. </NButton>
  455. <NButton
  456. v-if="useAuth().hasAuth('order:user:export')"
  457. size="small"
  458. type="primary"
  459. class="ml20px mt30px"
  460. ghost
  461. :loading="loading"
  462. @click="handleExportLog"
  463. >
  464. 导出记录
  465. </NButton>
  466. </template>
  467. <NDataTable
  468. v-model:checked-row-keys="checkedRowKeys"
  469. :columns="columns"
  470. :data="data"
  471. size="small"
  472. :flex-height="!appStore.isMobile"
  473. :scroll-x="1800"
  474. :loading="loading"
  475. :row-key="row => row.orderId"
  476. remote
  477. class="sm:h-full"
  478. :pagination="mobilePagination"
  479. />
  480. <NormalMoadl ref="orderMoadl" @finish="handleRefsh"></NormalMoadl>
  481. <DeliveryModal ref="Shipment" @finish="handleRefsh"></DeliveryModal>
  482. </NCard>
  483. <BasicModal @register="registerModalPrice" @after-leave="refresh">
  484. <LayoutTable>
  485. <ZTable
  486. :show-table-action="false"
  487. :columns="exportColumns"
  488. :api="fetchExportList"
  489. :default-params="{ exportType: 1 }"
  490. @register="registerTable"
  491. ></ZTable>
  492. </LayoutTable>
  493. </BasicModal>
  494. </div>
  495. </template>
  496. <style scoped></style>