|
|
@@ -0,0 +1,553 @@
|
|
|
+<script setup lang="tsx">
|
|
|
+import { nextTick, reactive, ref, unref, useTemplateRef, watch } from 'vue';
|
|
|
+import { NImage, NTag, useDialog } from 'naive-ui';
|
|
|
+import type { InternalRowData } from 'naive-ui/es/data-table/src/interface';
|
|
|
+import {
|
|
|
+ fetchBreakDownload,
|
|
|
+ fetchExportList,
|
|
|
+ fetchExportOrderList,
|
|
|
+ fetchGetDeliveryOrderList
|
|
|
+} from '@/service/api/delivery/normal-orde';
|
|
|
+import { fetchGetLoginUserList } from '@/service/api/common';
|
|
|
+import { useAppStore } from '@/store/modules/app';
|
|
|
+import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
|
|
+import { useAuth } from '@/hooks/business/auth';
|
|
|
+import { copyTextToClipboard } from '@/utils/zt';
|
|
|
+import { commonExport } from '@/utils/common';
|
|
|
+import { $t } from '@/locales';
|
|
|
+import { useForm } from '@/components/zt/Form/hooks/useForm';
|
|
|
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
|
|
|
+import { useTable } from '@/components/zt/Table/hooks/useTable';
|
|
|
+// import { type } from '../../../../packages/axios/src/index';
|
|
|
+import { SearchForm, dvyStatus } from './split-order';
|
|
|
+import NormalMoadl from './component/normal-modal.vue';
|
|
|
+const appStore = useAppStore();
|
|
|
+const checkedRowKeys = ref([]);
|
|
|
+const activeTab = ref('all');
|
|
|
+const orderMoadl = useTemplateRef('orderMoadl');
|
|
|
+const channelIdList = ref([]);
|
|
|
+const searchForm = ref();
|
|
|
+const searchParams = reactive({
|
|
|
+ current: 1,
|
|
|
+ size: 10
|
|
|
+});
|
|
|
+const [registerSearchForm, { getFieldsValue, setFieldsValue }] = useForm({
|
|
|
+ schemas: [
|
|
|
+ {
|
|
|
+ field: 'channelIdList',
|
|
|
+ label: '所属企业',
|
|
|
+ component: 'ApiSelect',
|
|
|
+ componentProps: {
|
|
|
+ api: () => fetchGetLoginUserList(),
|
|
|
+ labelFeild: 'channelName',
|
|
|
+ valueFeild: 'id',
|
|
|
+ multiple: true,
|
|
|
+ onUpdateValue: () => {
|
|
|
+ nextTick(() => {
|
|
|
+ handleSearch();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ getOptions: async (options: any) => {
|
|
|
+ await setFieldsValue({ channelIdList: [options[0].id] });
|
|
|
+ handleSearch();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ...SearchForm
|
|
|
+ ],
|
|
|
+ showAdvancedButton: false,
|
|
|
+ labelWidth: 120,
|
|
|
+ layout: 'horizontal',
|
|
|
+ size: 'small',
|
|
|
+ gridProps: {
|
|
|
+ cols: '1 xl:4 s:1 l:3',
|
|
|
+ itemResponsive: true
|
|
|
+ },
|
|
|
+ collapsedRows: 1
|
|
|
+});
|
|
|
+const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedTable({
|
|
|
+ api: () =>
|
|
|
+ fetchGetDeliveryOrderList({
|
|
|
+ ...searchParams,
|
|
|
+ orderLevel: 0,
|
|
|
+ splitStatus: 4,
|
|
|
+ orderStatus: activeTab.value,
|
|
|
+ ...unref(searchForm)
|
|
|
+ }),
|
|
|
+ transform: response => defaultTransform(response),
|
|
|
+ immediate: false,
|
|
|
+ paginationProps: {
|
|
|
+ pageSizes: import.meta.env.VITE_PAGE_SIZE.split(',').map(Number)
|
|
|
+ },
|
|
|
+ onPaginationParamsChange: params => {
|
|
|
+ searchParams.current = Number(params.page);
|
|
|
+ searchParams.size = Number(params.pageSize);
|
|
|
+ },
|
|
|
+ columns: () => [
|
|
|
+ {
|
|
|
+ key: 'orderItems',
|
|
|
+ title: '商品',
|
|
|
+ align: 'left',
|
|
|
+ width: 260,
|
|
|
+ colSpan: (_rowData, _rowIndex) => 2,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <div class={'mb3 flex items-center'}>
|
|
|
+ <n-tag>
|
|
|
+ <div class={'flex items-center'}>
|
|
|
+ {row.parentOrderNumber && <div class={''}>父订单编号:{row.parentOrderNumber}</div>}
|
|
|
+ <div onClick={() => handleCopy(row, 'parentOrderNumber')}>
|
|
|
+ <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
|
|
|
+ </div>
|
|
|
+ {row.orderLevel === 0 ? '订单编号' : '子订单编号'}:{row.orderNumber}
|
|
|
+ <div onClick={() => handleCopy(row, 'orderNumber')}>
|
|
|
+ <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
|
|
|
+ </div>
|
|
|
+ 下单时间:
|
|
|
+ {row.createTime} 门店名称 {row.shopName}
|
|
|
+ </div>
|
|
|
+ </n-tag>
|
|
|
+ </div>
|
|
|
+ {row.orderItems?.map(item => {
|
|
|
+ return (
|
|
|
+ <div class={'mb-3 h-80px flex items-center'}>
|
|
|
+ <NImage src={item.pic} class="h-[80px] min-w-80px w-[80px]" lazy />
|
|
|
+ <div class={'ml12px flex-1'}>
|
|
|
+ <div class={'w-full flex items-center justify-between'}>
|
|
|
+ <div class={'w200px'}>
|
|
|
+ <n-ellipsis class={'w200px'}>
|
|
|
+ <span class={'w-full text-left text-15px font-semibold'}>{item.skuName}</span>
|
|
|
+ </n-ellipsis>
|
|
|
+ </div>
|
|
|
+ <div class={'w150px pl30px text-left'}>
|
|
|
+ <div class={'text-16px font-semibold'}>¥{item.price} </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class={'w-full flex items-center justify-between'}>
|
|
|
+ <div class={'w200px text-gray'}>规格: {item.spec}</div>
|
|
|
+ <div class={'w150px pl30px text-left text-gray'}>x{item.prodCount} </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'deptName',
|
|
|
+ title: '单价(元)/数量',
|
|
|
+ align: 'center',
|
|
|
+ width: 100
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'actualTotal',
|
|
|
+ title: '实付金额(元)',
|
|
|
+ align: 'center',
|
|
|
+ width: 120,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div class={'mt7'}>
|
|
|
+ <div class={'text-16px text-#ff0000 font-semibold'}>{row.actualTotal} 元</div>
|
|
|
+ <div>共 {row.goodsTotalCount} 件</div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'payType',
|
|
|
+ title: '支付方式',
|
|
|
+ align: 'center',
|
|
|
+ width: 120,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <NTag class={'mt7'} type="success">
|
|
|
+ {row.payType == 0 || row.payType == 1 ? '微信' : '未支付'}
|
|
|
+ </NTag>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'seq',
|
|
|
+ title: '买家/收货人',
|
|
|
+ align: 'center',
|
|
|
+ width: 120,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div class={'mt7'}>
|
|
|
+ <div>{row.receiver}</div>
|
|
|
+ <div>{row.userMobile}</div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'dvyType',
|
|
|
+ title: '订单类型',
|
|
|
+ align: 'center',
|
|
|
+ width: 120,
|
|
|
+ render: row => {
|
|
|
+ return <NTag class={'mt7'}>{dvyStatus[row.dvyType as keyof typeof dvyStatus] || '未知类型'}</NTag>;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'status',
|
|
|
+ title: '拆单方式',
|
|
|
+ align: 'center',
|
|
|
+ width: 320,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div class={'mt7'}>
|
|
|
+ <div>{row.autoSplit == 1 ? '自动拆单' : '手动拆单'}</div>
|
|
|
+ {row.autoSplit === 0 && <div>{row.splitUserName}</div>}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ {
|
|
|
+ key: 'operate',
|
|
|
+ title: $t('common.operate'),
|
|
|
+ align: 'center',
|
|
|
+ width: 150,
|
|
|
+ fixed: 'right',
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div class={'mt7'}>
|
|
|
+ <n-button size="small" type="primary" ghost onClick={() => handleOpenMoadl(row)}>
|
|
|
+ 查看订单
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+});
|
|
|
+
|
|
|
+const [registerTable, { refresh, setTableLoading }] = useTable({
|
|
|
+ tableConfig: {
|
|
|
+ keyField: 'id',
|
|
|
+ title: '',
|
|
|
+ showAddButton: false,
|
|
|
+ showTableHeaderAction: false,
|
|
|
+ showSearch: false,
|
|
|
+ minHeight: 400
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const exportColumns: NaiveUI.TableColumn<InternalRowData>[] = [
|
|
|
+ {
|
|
|
+ key: 'index',
|
|
|
+ title: '序号',
|
|
|
+ align: 'center',
|
|
|
+ width: 100,
|
|
|
+ render(_, rowIndex) {
|
|
|
+ return rowIndex + 1;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'taskName',
|
|
|
+ title: '任务名称',
|
|
|
+ align: 'center',
|
|
|
+ minWidth: 100
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'updateTime',
|
|
|
+ title: '时间',
|
|
|
+ align: 'center',
|
|
|
+ minWidth: 100,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <div>创建时间:{row.createTime}</div>
|
|
|
+ <div>完成时间:{row.updateTime}</div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'operator',
|
|
|
+ title: '操作人',
|
|
|
+ align: 'center',
|
|
|
+ minWidth: 100
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'exportStatus',
|
|
|
+ title: '状态',
|
|
|
+ align: 'center',
|
|
|
+ minWidth: 100,
|
|
|
+ render: row => {
|
|
|
+ if (row.exportStatus == 0) {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ 请耐心等待,正在导出中...
|
|
|
+ <n-button text type="info" onClick={() => handleBreak(row.id as string)}>
|
|
|
+ 中断
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ } else if (row.exportStatus == 1) {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ 未下载
|
|
|
+ <n-button text type="info" onClick={() => handleDownload(row.id as string)}>
|
|
|
+ 下载
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ } else if (row.exportStatus == 2) {
|
|
|
+ return <div class={'text-gray-500'}>生成文件失败</div>;
|
|
|
+ } else if (row.exportStatus == 3) {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ 已下载
|
|
|
+ <n-button text type="info" onClick={() => handleDownload(row.id as string)}>
|
|
|
+ 下载
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ } else if (row.exportStatus == 4) {
|
|
|
+ return <div>导出失败</div>;
|
|
|
+ }
|
|
|
+ return <div>进行中</div>;
|
|
|
+ }
|
|
|
+ }
|
|
|
+];
|
|
|
+
|
|
|
+async function handleDownload(id: string) {
|
|
|
+ setTableLoading(true);
|
|
|
+ await commonExport('/platform/exportTask/download', { fileId: id }, '正常订单列表.xlsx');
|
|
|
+ refresh();
|
|
|
+}
|
|
|
+async function handleBreak(id: string) {
|
|
|
+ setTableLoading(true);
|
|
|
+ await fetchBreakDownload(id);
|
|
|
+ refresh();
|
|
|
+}
|
|
|
+
|
|
|
+const [registerModalPrice, { openModal }] = useModal({
|
|
|
+ title: '导出记录',
|
|
|
+ width: 1200,
|
|
|
+ height: 600,
|
|
|
+ showFooter: false
|
|
|
+});
|
|
|
+
|
|
|
+const dialog = useDialog();
|
|
|
+
|
|
|
+function handleOpenMoadl(row: Api.delivery.deliveryOrder) {
|
|
|
+ if (!row.orderNumber) {
|
|
|
+ window.$message?.error('订单异常');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ orderMoadl.value?.open(String(row.orderNumber));
|
|
|
+}
|
|
|
+async function getNums() {
|
|
|
+ // const form = getFieldsValue();
|
|
|
+ // const params = {
|
|
|
+ // ...form,
|
|
|
+ // channelIdList: channelIdList.value,
|
|
|
+ // startTime: form.createTime ? form.createTime[0] : null,
|
|
|
+ // endTime: form.createTime ? form.createTime[1] : null,
|
|
|
+ // createTime: null
|
|
|
+ // };
|
|
|
+ // const { data: keyData } = await fetchGetDeliveryStatusNum(params);
|
|
|
+ // if (!keyData) return;
|
|
|
+ // const orderStatusList = [
|
|
|
+ // {
|
|
|
+ // label: '全部',
|
|
|
+ // value: 'all'
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // label: '待支付',
|
|
|
+ // value: 'paddingPay'
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // label: '待拆单',
|
|
|
+ // value: 'split'
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // label: '待发货',
|
|
|
+ // value: 'paddingShipped'
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // label: '待收货',
|
|
|
+ // value: 'paddingReceived'
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // label: '已完成',
|
|
|
+ // value: 'completed'
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // label: '已取消',
|
|
|
+ // value: 'cancel'
|
|
|
+ // }
|
|
|
+ // ];
|
|
|
+ // const updatedOrderStatusList = orderStatusList.map(item => {
|
|
|
+ // const key = item.value as keyof typeof keyData;
|
|
|
+ // if (Object.hasOwn(keyData, key)) {
|
|
|
+ // return {
|
|
|
+ // ...item,
|
|
|
+ // num: keyData[key]
|
|
|
+ // };
|
|
|
+ // }
|
|
|
+ // return item;
|
|
|
+ // });
|
|
|
+ // // console.log(updatedOrderStatusList, 'updatedOrderStatusList');
|
|
|
+ // statusList.value = updatedOrderStatusList;
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [activeTab.value],
|
|
|
+ () => {
|
|
|
+ searchParams.current = 1;
|
|
|
+ getData();
|
|
|
+ }
|
|
|
+);
|
|
|
+function handleSearch() {
|
|
|
+ const form = getFieldsValue();
|
|
|
+ if (form.createTime) {
|
|
|
+ form.startTime = form.createTime[0];
|
|
|
+ form.endTime = form.createTime[1];
|
|
|
+ delete form.createTime;
|
|
|
+ }
|
|
|
+ channelIdList.value = form.channelIdList;
|
|
|
+ searchForm.value = form;
|
|
|
+ getData();
|
|
|
+ getNums();
|
|
|
+}
|
|
|
+function handleReset() {
|
|
|
+ searchForm.value = getFieldsValue();
|
|
|
+ searchForm.value.channelIdList = channelIdList.value;
|
|
|
+ setFieldsValue({ channelIdList: channelIdList.value });
|
|
|
+ getData();
|
|
|
+ getNums();
|
|
|
+}
|
|
|
+async function handleCopy(row: Api.delivery.deliveryOrder, key: string) {
|
|
|
+ if (!row[key]) {
|
|
|
+ window.$message?.error('订单编号不存在');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await copyTextToClipboard(row[key]);
|
|
|
+}
|
|
|
+function handleRefsh() {
|
|
|
+ getData();
|
|
|
+ getNums();
|
|
|
+}
|
|
|
+async function handleExport() {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ // await commonExport(
|
|
|
+ // '/platform/order/export',
|
|
|
+ // { ...getFieldsValue(), orderStatus: activeTab.value },
|
|
|
+ // '正常订单列表.xlsx'
|
|
|
+ // );
|
|
|
+ const newParams = getFieldsValue();
|
|
|
+ if (newParams.createTime) {
|
|
|
+ newParams.startTime = newParams.createTime[0];
|
|
|
+ newParams.endTime = newParams.createTime[1];
|
|
|
+ delete newParams.createTime;
|
|
|
+ }
|
|
|
+ await fetchExportOrderList({ ...newParams, orderStatus: activeTab.value });
|
|
|
+ dialog.success({
|
|
|
+ title: '提示',
|
|
|
+ content: () => {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <p>导出操作进行中......</p>
|
|
|
+ <p>是否进入导出记录</p>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ positiveText: '确定',
|
|
|
+ negativeText: '取消',
|
|
|
+ onPositiveClick: () => {
|
|
|
+ openModal();
|
|
|
+ },
|
|
|
+ onNegativeClick: () => {}
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleExportLog() {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ openModal();
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
|
|
+ <NCard :bordered="false" size="small">
|
|
|
+ <NCollapse display-directive="show" default-expanded-names="search">
|
|
|
+ <NCollapseItem title="搜索" name="search">
|
|
|
+ <BasicForm @register-form="registerSearchForm" @submit="handleSearch" @reset="handleReset" />
|
|
|
+ </NCollapseItem>
|
|
|
+ </NCollapse>
|
|
|
+ </NCard>
|
|
|
+ <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
|
|
+ <template #header-extra>
|
|
|
+ <NButton
|
|
|
+ v-if="useAuth().hasAuth('order:user:export')"
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ class="ml20px mt30px"
|
|
|
+ ghost
|
|
|
+ :loading="loading"
|
|
|
+ :disabled="data.length == 0"
|
|
|
+ @click="handleExport"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <SvgIcon icon="mingcute:file-export-line"></SvgIcon>
|
|
|
+ </template>
|
|
|
+ 导出
|
|
|
+ </NButton>
|
|
|
+ <NButton
|
|
|
+ v-if="useAuth().hasAuth('order:user:export')"
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ class="ml20px mt30px"
|
|
|
+ ghost
|
|
|
+ :loading="loading"
|
|
|
+ @click="handleExportLog"
|
|
|
+ >
|
|
|
+ 导出记录
|
|
|
+ </NButton>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <NDataTable
|
|
|
+ v-model:checked-row-keys="checkedRowKeys"
|
|
|
+ :columns="columns"
|
|
|
+ :data="data"
|
|
|
+ size="small"
|
|
|
+ :flex-height="!appStore.isMobile"
|
|
|
+ :scroll-x="1800"
|
|
|
+ :loading="loading"
|
|
|
+ :row-key="row => row.orderId"
|
|
|
+ remote
|
|
|
+ class="sm:h-full"
|
|
|
+ :pagination="mobilePagination"
|
|
|
+ />
|
|
|
+ <NormalMoadl ref="orderMoadl" @finish="handleRefsh"></NormalMoadl>
|
|
|
+ </NCard>
|
|
|
+ <BasicModal @register="registerModalPrice" @after-leave="refresh">
|
|
|
+ <LayoutTable>
|
|
|
+ <ZTable
|
|
|
+ :show-table-action="false"
|
|
|
+ :columns="exportColumns"
|
|
|
+ :api="fetchExportList"
|
|
|
+ :default-params="{ exportType: 1 }"
|
|
|
+ @register="registerTable"
|
|
|
+ ></ZTable>
|
|
|
+ </LayoutTable>
|
|
|
+ </BasicModal>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped></style>
|