index.vue 15 KB

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