|
|
@@ -0,0 +1,320 @@
|
|
|
+<script setup lang="tsx">
|
|
|
+import { reactive, ref, unref, useTemplateRef, watch } from 'vue';
|
|
|
+import { NImage, NTag } from 'naive-ui';
|
|
|
+import { fetchGetAfterSalesOrderList, fetchGetAfterSalesStatusNum } from '@/service/api/delivery/after-sales-order';
|
|
|
+import { useAppStore } from '@/store/modules/app';
|
|
|
+import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
|
|
+import { copyTextToClipboard } from '@/utils/zt';
|
|
|
+import { $t } from '@/locales';
|
|
|
+import { useForm } from '@/components/zt/Form/hooks/useForm';
|
|
|
+import NormalMoadl from '../normal-order/component/normal-modal.vue';
|
|
|
+import { SearchForm, orderStatus } from '../normal-order/normal-order';
|
|
|
+import { refundEnum, refundStatus } from './after-sales-order';
|
|
|
+const appStore = useAppStore();
|
|
|
+const checkedRowKeys = ref([]);
|
|
|
+const activeTab = ref(0);
|
|
|
+const statusList = ref<{ label: string; value: number; num?: number; key: string }[]>([]);
|
|
|
+const orderMoadl = useTemplateRef('orderMoadl');
|
|
|
+
|
|
|
+const [registerSearchForm, { getFieldsValue }] = useForm({
|
|
|
+ schemas: SearchForm,
|
|
|
+ showAdvancedButton: false,
|
|
|
+ labelWidth: 120,
|
|
|
+ layout: 'horizontal',
|
|
|
+ gridProps: {
|
|
|
+ cols: '1 xl:4 s:1 l:3',
|
|
|
+ itemResponsive: true
|
|
|
+ },
|
|
|
+ collapsedRows: 1
|
|
|
+});
|
|
|
+const searchForm = ref();
|
|
|
+const searchParams = reactive({
|
|
|
+ current: 1,
|
|
|
+ size: 10
|
|
|
+});
|
|
|
+const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedTable({
|
|
|
+ api: () => fetchGetAfterSalesOrderList({ ...searchParams, returnMoneySts: activeTab.value, ...unref(searchForm) }),
|
|
|
+ transform: response => defaultTransform(response),
|
|
|
+ paginationProps: {
|
|
|
+ pageSizes: [10, 20, 50, 100, 150, 200]
|
|
|
+ },
|
|
|
+ onPaginationParamsChange: params => {
|
|
|
+ searchParams.current = Number(params.page);
|
|
|
+ searchParams.size = Number(params.pageSize);
|
|
|
+ },
|
|
|
+ columns: () => [
|
|
|
+ {
|
|
|
+ key: 'orderItems',
|
|
|
+ title: '商品',
|
|
|
+ align: 'left',
|
|
|
+ width: 200,
|
|
|
+ colSpan: (_rowData, _rowIndex) => 2,
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <div class={'mb3 flex items-center'}>
|
|
|
+ <n-tag>
|
|
|
+ <div class={'flex items-center'}>
|
|
|
+ 退款编号:{row.refundSn}
|
|
|
+ <div onClick={() => handleCopy(row.refundSn)}>
|
|
|
+ <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
|
|
|
+ </div>
|
|
|
+ 订单编号:{row.orderNumber}
|
|
|
+ <div onClick={() => handleCopy(row.orderNumber)}>
|
|
|
+ <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
|
|
|
+ </div>
|
|
|
+ 申请时间:
|
|
|
+ {row.applyTime} 门店名称 {row.shopName}
|
|
|
+ </div>
|
|
|
+ </n-tag>
|
|
|
+ </div>
|
|
|
+ {row.orderRefundSkuList?.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={'w250px'}>
|
|
|
+ <n-ellipsis class={'w250px'}>
|
|
|
+ <span class={'w-full text-left text-15px font-semibold'}>{item.skuName}</span>
|
|
|
+ </n-ellipsis>
|
|
|
+ </div>
|
|
|
+ <div class={'w100px text-left'}>
|
|
|
+ <div class={'text-16px font-semibold'}>¥{item.skuPrice} </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class={'w-full flex items-center justify-between'}>
|
|
|
+ <div class={'w250px text-gray'}>规格:{item.spec || '--'} </div>
|
|
|
+ <div class={'w100px text-left text-gray'}>x{item.productCount} </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: 'refundAmount',
|
|
|
+ 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>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '商品状况',
|
|
|
+ key: 'goods',
|
|
|
+ width: 220,
|
|
|
+ align: 'center',
|
|
|
+ render: row => {
|
|
|
+ const TypeText = ['仅退款', '退款退货', '差价退款'];
|
|
|
+ return (
|
|
|
+ <div class={'mt7'}>
|
|
|
+ <div class={'text-16px font-semibold'}>{TypeText[Number(row.applyType) - 1] || '未知状况'} </div>
|
|
|
+ <div class={'text-#ff0000'}> {row.buyerReason} </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'status',
|
|
|
+ title: '订单状态',
|
|
|
+ align: 'center',
|
|
|
+ width: 220,
|
|
|
+ render: row => {
|
|
|
+ const statusKey = row.hbOrderStatus as keyof typeof orderStatus;
|
|
|
+ const statusText = orderStatus[statusKey] || '未知状态';
|
|
|
+ return <NTag class={'mt7'}>{statusText}</NTag>;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'createTime',
|
|
|
+ title: '售后状态',
|
|
|
+ align: 'center',
|
|
|
+ width: 160,
|
|
|
+ render: row => {
|
|
|
+ const statusKey = row.returnMoneySts as keyof typeof refundStatus;
|
|
|
+ const statusText = refundStatus[statusKey] || '暂无售后';
|
|
|
+ return <NTag class={'mt7'}>{statusText}</NTag>;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'operate',
|
|
|
+ title: $t('common.operate'),
|
|
|
+ align: 'center',
|
|
|
+ width: 130,
|
|
|
+ fixed: 'right',
|
|
|
+ render: row => {
|
|
|
+ return (
|
|
|
+ <div class={'mt7'}>
|
|
|
+ <n-button size="small" type="primary" quaternary onClick={() => handleOpenMoadl(row)}>
|
|
|
+ 查看订单
|
|
|
+ </n-button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+});
|
|
|
+function handleOpenMoadl(row: Api.delivery.OrderRefund) {
|
|
|
+ if (!row.orderNumber) {
|
|
|
+ window.$message?.error('订单异常');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ orderMoadl.value?.open(String(row.orderNumber));
|
|
|
+}
|
|
|
+async function getNums() {
|
|
|
+ const { data: keyData } = await fetchGetAfterSalesStatusNum({ ...getFieldsValue() });
|
|
|
+ if (!keyData) return;
|
|
|
+ const orderStatusList = [
|
|
|
+ {
|
|
|
+ label: '全部',
|
|
|
+ value: 0,
|
|
|
+ key: 'allCount'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '买家申请',
|
|
|
+ value: refundEnum.BUYER_APPLY,
|
|
|
+ key: 'sellerApplyCount'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '卖家拒绝',
|
|
|
+ value: refundEnum.SELLER_REFUSE,
|
|
|
+ key: 'sellerAcceptCount'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '买家发货',
|
|
|
+ value: refundEnum.BUYER_DELIVERY,
|
|
|
+ key: 'sellerAcceptCount'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '卖家接受',
|
|
|
+ value: refundEnum.SELLER_AGREE,
|
|
|
+ key: 'buyerDeliveryCount'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '退款成功',
|
|
|
+ value: refundEnum.REFUND_SUCCESS,
|
|
|
+ key: 'refundCompleteCount'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '撤回申请',
|
|
|
+ value: refundEnum.REVOKE_APPLY,
|
|
|
+ key: 'withdrawApplyCount'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ const updatedOrderStatusList = orderStatusList.map(item => {
|
|
|
+ if (Object.hasOwn(keyData, item.key)) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ num: keyData[item.key]
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ });
|
|
|
+ console.log(updatedOrderStatusList, 'updatedOrderStatusList');
|
|
|
+ statusList.value = updatedOrderStatusList;
|
|
|
+}
|
|
|
+getNums();
|
|
|
+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;
|
|
|
+ }
|
|
|
+ searchForm.value = form;
|
|
|
+ getData();
|
|
|
+ getNums();
|
|
|
+}
|
|
|
+function handleReset() {
|
|
|
+ searchForm.value = getFieldsValue();
|
|
|
+ getData();
|
|
|
+}
|
|
|
+async function handleCopy(No: string) {
|
|
|
+ await copyTextToClipboard(No);
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
|
|
+ <NCard :bordered="false" size="small">
|
|
|
+ <NCollapse display-directive="show">
|
|
|
+ <NCollapseItem title="搜索">
|
|
|
+ <BasicForm @register-form="registerSearchForm" @submit="handleSearch" @reset="handleReset" />
|
|
|
+ </NCollapseItem>
|
|
|
+ </NCollapse>
|
|
|
+ </NCard>
|
|
|
+ <NCard title="售后列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
|
|
|
+ <NTabs v-model:value="activeTab" type="line" animated class="mb-16px h-full">
|
|
|
+ <NTabPane
|
|
|
+ v-for="item in statusList"
|
|
|
+ :key="item.value"
|
|
|
+ display-directive="show"
|
|
|
+ :name="item.value"
|
|
|
+ :tab="`${item.label}(${item.num})`"
|
|
|
+ >
|
|
|
+ <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.refundId"
|
|
|
+ remote
|
|
|
+ class="sm:h-full"
|
|
|
+ :pagination="mobilePagination"
|
|
|
+ />
|
|
|
+ </NTabPane>
|
|
|
+ </NTabs>
|
|
|
+
|
|
|
+ <NormalMoadl ref="orderMoadl" @finish="(getData, getNums)"></NormalMoadl>
|
|
|
+ </NCard>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+:deep(.n-tabs-pane-wrapper) {
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+:deep(.n-tab-pane) {
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+</style>
|