张田田 2 месяцев назад
Родитель
Сommit
45ddd53008

+ 81 - 19
src/model/order.model.ts

@@ -44,7 +44,7 @@ export interface OrderModel {
   types: string[];//订单类型 1-未派单 2-已派单但未核销 3-已核销	
   pieTimeStart: string;//派单时间开始
   pieTimeEnd: string;//派单时间结束
-  orderByType:number;
+  orderByType: number;
 }
 export type OrderQuery = Partial<OrderModel>;
 //派单机构列表
@@ -165,7 +165,7 @@ export interface EvaluateDetailModel {
   createTime: string;//创建时间
 }
 
-export interface ApplyRecordModel{
+export interface ApplyRecordModel {
   id: number; // 主键ID
   arrangeTime: string; // 预定时间
   arrangeDuration: number; // 预定时长
@@ -181,23 +181,85 @@ export interface ApplyRecordModel{
   status: string; // 服务状态
 }
 
-export interface LogisticsModel{
-  trailUrl:string;
-  arrivalTime:string;
-  totalTime:string;
-  remainTime:string;
-  tracks:[
+export interface LogisticsModel {
+  trailUrl: string;
+  arrivalTime: string;
+  totalTime: string;
+  remainTime: string;
+  tracks: [
     {
-      time:string;
-      context:string;
-      ftime:string;
-      areaCode:string;
-      areaName:string;
-      status:string;
-      areaCenter:string;
-      areaPinYin:string;
-      statusCode:string;
-      location:string;
+      time: string;
+      context: string;
+      ftime: string;
+      areaCode: string;
+      areaName: string;
+      status: string;
+      areaCenter: string;
+      areaPinYin: string;
+      statusCode: string;
+      location: string;
     }
   ]
-}
+}
+
+// 售后
+export interface AfterSaleModel {
+  id: number; // 主键ID
+  orderNo: string; // 订单编号
+  type: string; // 售后类型
+  typeStr: string; // 售后类型文字
+  ref: string; // 关联标识
+  updateTime: string; // 更新时间
+  patientConditioningProgramId: number; // 患者调理方案ID
+  conditioningProgramName: string; // 商品名称
+  conditioningProgramPhoto: string; // 商品图片
+  sellType: string; // 商品类型 1-实体商品 2-线下服务 3-线上权益
+  sellTypeStr: string; // 商品类型文字
+  pricingUnit: string; // 计价单位
+  unitPrice: number; // 单价
+  convertUnit: string; // 换算单位
+  convertDose: number; // 换算剂量
+  totalMeasure: number; // 总用量
+  totalPrice: number; // 总价格
+  liaison: string; // 联系人
+  progress: string; // 售后进度
+  progressStr: string; // 售后进度文字
+  expressStatus: string; // 物流状态
+  sequence: number; // 次序
+  finishRatio: string; // 完成比例
+  refundAmount: number; // 退款金额
+  applyAmount: number; // 申请金额
+  reviewAmount: number; // 审核金额
+  reason: string; // 原因
+  voucher: string; // 凭证
+  applyTime: string; // 申请时间
+  finishTime: string; // 完结时间
+  receiptStatus: string; // 商品申请售后时的状态
+  receiptStatusStr: string; // 商品申请售后时的状态文字
+  receiptType: string; // 收货状态 1-已收到货 2-未收到货
+  receiptTypeStr: string; // 收货状态文字
+  remark: string; // 退款说明
+  voucherImgs: string[]; // 退款凭证图片集合
+  title: string; // 节点标题
+  content: string; // 节点内容描述
+  operaTime: string; // 操作时间
+  handleEndTime: string; // 处理截止时间
+}
+export type AfterSaleQuery = Partial<AfterSaleModel>;
+
+// 售后协商历史
+export interface AfterSaleNegotiateModel {
+  title: string; // 标题
+  progress: string;//售后进度 0-发起售后 1-撤销售后 2-审核拒绝 3-审核通过 4-退款完成
+  content: string; // 内容描述
+  createTime: string; // 操作时间
+  createBy: string;//操作者
+  aftersaleContent: string; // 售后申请信息
+}
+
+// 售后审核
+export interface AfterSaleAuditModel {
+  id: number; // 售后ID
+  isAgree: boolean; // 是否同意
+  reason: string; // 拒绝理由
+}

+ 47 - 187
src/pages/index/order/afterSale.vue

@@ -1,22 +1,17 @@
 <script setup lang="ts">
-import { h, ref, reactive, shallowRef, onMounted, toRaw, computed } from 'vue';
-import EditShipment from '@/order/EditShipment.vue';
+import { h, ref, reactive, shallowRef, onMounted, toRaw } from 'vue';
 import OrderDetail from '@/service/OrderDetail.vue';
-import LogisticsDetails from '@/order/LogisticsDetails.vue';
 import Negotiations from '@/order/Negotiations.vue';
 import SeeAmount from '@/order/SeeAmount.vue';
 import Amount from '@/order/Amount.vue';
 import Review from '@/order/Review.vue';
-import type { ShipmentModel, ShipmentQuery } from '@/model/order.model';
-import { getAllSupplierMethod } from '@/request/api/care.api';
+import type { AfterSaleModel, AfterSaleQuery } from '@/model/order.model';
 defineOptions({
-  name: 'ShipmentPage',
+  name: 'AfterSalePage',
 });
-import { receiptStatus, receiptType } from '@/model/options';
-import dayjs from 'dayjs';
 // 接口数据
 import {
-  getShipmentListMethod,
+  getAfterSaleMethod,
 } from '@/request/api/order.api';
 import { usePagination } from 'alova/client';
 
@@ -31,15 +26,20 @@ import type {
   VxeGridProps,
 } from 'vxe-table';
 
+const progressOptions = [
+  { label: '退款申请中', value: '0' },
+  { label: '撤销退款', value: '1' },
+  { label: '退款已拒绝', value: '2' },
+  { label: '退款已同意', value: '3' },
+  { label: '退款完成', value: '4' },
+];
 
-const model = shallowRef<ShipmentQuery>();
-const searchFormProps = reactive<VxeFormProps<ShipmentQuery>>({
+const model = shallowRef<AfterSaleQuery>();
+const searchFormProps = reactive<VxeFormProps<AfterSaleQuery>>({
   titleWidth: 100,
   titleAlign: 'right',
   titleColon: true,
-  data: {
-    receiptStatus: '0', // 默认待发货
-  },
+  data: {},
   items: [
     {
       field: 'orderNo',
@@ -48,27 +48,21 @@ const searchFormProps = reactive<VxeFormProps<ShipmentQuery>>({
       itemRender: { name: 'VxeInput', props: { placeholder: '请输入' } },
     },
     {
-      field: 'receiptStatus',
+      field: 'liaison',
       title: '收货人',
       span: 4,
-
-      itemRender: {
-        name: 'VxeSelect',
-        options: receiptStatus,
-        props: { placeholder: '请选择', clearable: true },
-      },
+      itemRender: { name: 'VxeInput', props: { placeholder: '请输入' } },
     },
     {
-      field: 'receiptType',
+      field: 'progress',
       title: '退款状态',
       span: 4,
       itemRender: {
         name: 'VxeSelect',
-        options: receiptType,
-        props: { placeholder: '请选择' },
+        options: progressOptions,
+        props: { placeholder: '请选择', clearable: true },
       },
     },
-
     {
       span: 8,
       itemRender: {
@@ -82,88 +76,24 @@ const searchFormProps = reactive<VxeFormProps<ShipmentQuery>>({
     },
   ],
 });
-const searchFormEmits: VxeFormListeners<ShipmentQuery> = {
+const searchFormEmits: VxeFormListeners<AfterSaleQuery> = {
   submit({ data }) {
-    model.value = {
-      ...data,
-      payTimeStart: payTimeStart.value ? dayjs(payTimeStart.value).format('YYYY-MM-DD HH:mm:ss') : '',
-      payTimeEnd: payTimeEnd.value ? dayjs(payTimeEnd.value).format('YYYY-MM-DD HH:mm:ss') : '',
-    } as any;
+    model.value = { ...data } as any;
   },
-  // 重置
   reset({ data }) {
     model.value = { ...data } as any;
-    payTimeStart.value = '';
-    payTimeEnd.value = '';
   },
 };
-const processedData = ref<ShipmentModel[]>([]);
-const orderGroupMap = ref<Map<string, { startIndex: number; count: number; rows: ShipmentModel[] }>>(new Map());
-
-function getGroupKey(item: ShipmentModel): string {
-  const orderNo = item.orderNo || '';
-  const supplierId = item.conditioningProgramSupplierId || '';
-  return `${orderNo}_${supplierId}`;
-}
-
-function processDataForMerge(data: ShipmentModel[]) {
-  const grouped = new Map<string, ShipmentModel[]>();
-  const result: ShipmentModel[] = [];
-
-  data.forEach((item) => {
-    const groupKey = getGroupKey(item);
-    if (!grouped.has(groupKey)) {
-      grouped.set(groupKey, []);
-    }
-    grouped.get(groupKey)!.push(item);
-  });
-
-  orderGroupMap.value.clear();
-  let currentIndex = 0;
-
-  grouped.forEach((rows, groupKey) => {
-    const count = rows.length;
-    orderGroupMap.value.set(groupKey, {
-      startIndex: currentIndex,
-      count,
-      rows,
-    });
-    result.push(...rows);
-    currentIndex += count;
-  });
-
-  processedData.value = result;
-  return result;
-}
 
-const gridRef = ref<VxeGridInstance<ShipmentModel>>();
-const gridOptions = reactive<VxeGridProps<ShipmentModel>>({
-  id: 'tag-list',
+const gridRef = ref<VxeGridInstance<AfterSaleModel>>();
+const gridOptions = reactive<VxeGridProps<AfterSaleModel>>({
+  id: 'after-sale-list',
   border: true,
   showOverflow: true,
   height: 'auto',
   autoResize: false,
   syncResize: true,
   scrollY: { enabled: false },
-  spanMethod: ({ row, column, rowIndex }: any) => {
-    const groupKey = getGroupKey(row);
-    const groupInfo = orderGroupMap.value.get(groupKey);
-
-    if (!groupInfo) return { rowspan: 1, colspan: 1 };
-
-    const { startIndex, count } = groupInfo;
-    const isFirstRow = rowIndex === startIndex;
-
-    if (column.field === 'orderNo') {
-      return isFirstRow ? { rowspan: count, colspan: 1 } : { rowspan: 0, colspan: 0 };
-    }
-
-    if (column.field === 'action') {
-      return isFirstRow ? { rowspan: count, colspan: 1 } : { rowspan: 0, colspan: 0 };
-    }
-
-    return { rowspan: 1, colspan: 1 };
-  },
   rowConfig: {
     isHover: true,
     isCurrent: true,
@@ -172,7 +102,6 @@ const gridOptions = reactive<VxeGridProps<ShipmentModel>>({
     custom: true,
     zoom: true,
     slots: {
-      // buttons: 'handle',
       tools: 'toolbar-extra',
     },
   },
@@ -185,18 +114,18 @@ const gridOptions = reactive<VxeGridProps<ShipmentModel>>({
   columns: [
     { type: 'seq', width: 70, fixed: 'left' },
     { field: 'orderNo', title: '订单编号', slots: { default: 'orderNoCell' } },
-    { field: 'payTime', title: '售后类型' },
-    { field: 'totalMeasure', title: '退款更新时间' },
+    { field: 'typeStr', title: '售后类型' },
+    { field: 'updateTime', title: '退款更新时间' },
     { field: 'conditioningProgramName', title: '项目名称' },
-    { field: 'totalMeasure', title: '商品类型' },
+    { field: 'sellTypeStr', title: '商品类型' },
     { field: 'totalPrice', title: '总价(元)' },
-    { field: 'pricingUnit', title: '收货人' },
-    { field: 'unitPrice', title: '退款状态' },
-    { field: 'receiptStatus', title: '物流状态', slots: { default: 'progressCell' } },
-    { field: 'receiptType', title: '核销情况', slots: { default: 'shipmentTypeCell' } },
-    { field: 'conditioningProgramSupplierName', title: '退款金额' },
-    { field: 'conditioningProgramSupplierName', title: '退款原因' },
-    { field: 'conditioningProgramSupplierName', title: '退款说明', slots: { default: 'refundReasonCell' } },
+    { field: 'liaison', title: '收货人' },
+    { field: 'progressStr', title: '退款状态' },
+    { field: 'expressStatus', title: '物流状态' },
+    { field: 'finishRatio', title: '核销情况' },
+    { field: 'refundAmount', title: '退款金额' },
+    { field: 'reason', title: '退款原因' },
+    { field: 'remark', title: '退款说明', slots: { default: 'refundReasonCell' } },
     {
       field: 'action',
       title: '操作',
@@ -211,7 +140,7 @@ const gridOptions = reactive<VxeGridProps<ShipmentModel>>({
 const gridEvents: VxeGridListeners = {};
 
 const { loading, page, pageSize, total, onSuccess, refresh } = usePagination(
-  (page, size) => getShipmentListMethod(page, size, model.value),
+  (page, size) => getAfterSaleMethod(page, size, model.value),
   {
     initialData: { data: [], total: 0 },
     initialPage: 1,
@@ -221,32 +150,16 @@ const { loading, page, pageSize, total, onSuccess, refresh } = usePagination(
   }
 );
 onSuccess(({ data: { data } }) => {
-  const processed = processDataForMerge(data);
-  gridRef.value?.loadData(processed);
+  gridRef.value?.loadData(data);
 });
-const supplierOptions = ref<{ label: string; value: string }[]>([]);
-const supplierArr = ref<any[]>([]);
-// 获取所有的供应商
-async function getSupplier(params: any) {
-  supplierOptions.value = [];
-  const res = (await getAllSupplierMethod(params)) as any[];
-  if (Array.isArray(res) && res.length > 0) {
-    supplierArr.value = res;
-    supplierOptions.value = res.map((item: any) => ({
-      label: item.name,
-      value: item.id,
-    }));
-  }
-}
 
 onMounted(() => {
   model.value = toRaw(searchFormProps.data);
-  getSupplier({});
 });
 
 
 // 点击订单编号查看订单详情
-function serviceDetail(row?: ShipmentModel) {
+function serviceDetail(row?: AfterSaleModel) {
   if (!row) return;
   const types = 'record';
   VxeUI.modal.open({
@@ -262,7 +175,7 @@ function serviceDetail(row?: ShipmentModel) {
         return h(OrderDetail, {
           data: {
             ...row,
-            id: row.id,
+            id: row.patientConditioningProgramId,
             types,
           },
           onVoidSubmit() {
@@ -274,11 +187,10 @@ function serviceDetail(row?: ShipmentModel) {
   });
 }
 
-async function goShipment(row?: ShipmentModel) {
+async function goShipment(row?: AfterSaleModel) {
   if (!row) return;
-
-  const isReview = row.receiptStatus === '0'; // 待发货 -> 审核
-  const isEditAmount = row.receiptStatus === '1'; // 已发货 -> 金额
+  const isReview = row.progress === '0'; // 退款申请中 → 审核
+  const isEditAmount = row.progress === '3'; // 退款已同意 → 金额
   if (isReview) {
     VxeUI.modal.open({
       title: '审核',
@@ -308,7 +220,6 @@ async function goShipment(row?: ShipmentModel) {
     // 金额 or 查看金额
     const title = isEditAmount ? '退款金额' : '退款金额查看';
     const component = isEditAmount ? Amount : SeeAmount;
-    
     VxeUI.modal.open({
       title,
       height: 500,
@@ -322,7 +233,6 @@ async function goShipment(row?: ShipmentModel) {
             data: row,
             onSubmit: (amount: number) => {
               console.log('Final Amount:', amount);
-              // TODO: API call to update amount
               refresh(page.value);
               VxeUI.modal.close('amount-edit-modal');
             },
@@ -336,30 +246,8 @@ async function goShipment(row?: ShipmentModel) {
   }
 }
 
-// 查看物流详情
-function viewLogistics(row?: ShipmentModel) {
+function goAfterSale(row?: AfterSaleModel) {
   if (!row) return;
-  VxeUI.modal.open({
-    title: '物流详情',
-    height: 800,
-    width: 1000,
-    escClosable: true,
-    destroyOnClose: true,
-    id: 'logistics-modal',
-    slots: {
-      default() {
-        return h(LogisticsDetails, {
-              data: row
-        });
-      }
-    }
-  });
-}
-
-function goAfterSale(row?: ShipmentModel) {
-  if (!row) return;
-  // TODO: Implement after sale details or history
-  console.log('goAfterSale', row);
   VxeUI.modal.open({
     title: '协商历史',
     height: 800,
@@ -370,56 +258,32 @@ function goAfterSale(row?: ShipmentModel) {
     slots: {
       default() {
         return h(Negotiations, {
-              data: row
+          data: row
         });
       }
     }
-  }); 
-}
-
-const payTimeStart = ref<string>('');
-const payTimeEnd = ref<string>('');
-function disabledPayEndDate(current: any) {
-  if (!payTimeStart.value) return false;
-  return current && current < dayjs(payTimeStart.value);
+  });
 }
 </script>
 <template>
   <div class="page-container flex flex-col">
     <header class="flex-none mt-4">
-      <vxe-form v-bind="searchFormProps" v-on="searchFormEmits">
-        <template #payTimeRange>
-          <div class="date-range-container">
-            <a-date-picker v-model:value="payTimeStart" placeholder="请选择开始时间" style="flex: 1"
-              :show-time="{ format: 'HH:mm' }" />
-            <span class="date-separator">至</span>
-            <a-date-picker v-model:value="payTimeEnd" placeholder="请选择结束时间" style="flex: 1"
-              :disabledDate="disabledPayEndDate" :show-time="{ format: 'HH:mm' }" />
-          </div>
-        </template>
-      </vxe-form>
+      <vxe-form v-bind="searchFormProps" v-on="searchFormEmits"></vxe-form>
     </header>
     <main class="flex-auto overflow-hidden">
       <vxe-grid ref="gridRef" v-bind="gridOptions" v-on="gridEvents" :loading="loading">
         <template #orderNoCell="{ row }">
           <span class="order-no-link" @click="serviceDetail(row)">{{ row.orderNo }}</span>
         </template>
-        <template #progressCell="{ row }">
-          <span class="order-no-link" @click="viewLogistics(row)">{{ row.receiptStatus === '0' ? '待发货' : row.receiptStatus === '1' ? '已发货' : row.receiptStatus === '2' ?
-            '已收货' : '' }}</span>
-        </template>
-        <template #shipmentTypeCell="{ row }">
-          <div>{{ row.receiptType === '0' ? '配送' : row.receiptType === '1' ? '线下取货' : '' }}</div>
-        </template>
         <template #refundReasonCell="{ row }">
-          <span class="order-no-link" @click="goAfterSale(row)">{{ row.conditioningProgramSupplierName }}</span>
+          <span class="order-no-link" @click="goAfterSale(row)">{{ row.remark }}</span>
         </template>
         <template #action="{ row }">
           <vxe-button mode="text" status="primary" @click="goAfterSale(row)">
             协商历史
           </vxe-button>
           <vxe-button mode="text" status="primary" @click="goShipment(row)">
-            {{ row.receiptStatus === '0' ? '审核' : row.receiptStatus === '1' ? '金额' : '查看金额' }}
+            {{ row.progress === '0' ? '审核' : row.progress === '3' ? '金额' : '查看金额' }}
           </vxe-button>
         </template>
         <template #toolbar-extra>
@@ -444,10 +308,6 @@ function disabledPayEndDate(current: any) {
   </div>
 </template>
 <style scoped lang="scss">
-.date-separator {
-  margin: 0 10px;
-}
-
 .page-container {
   padding: 0 24px;
   max-height: var(--page-main-container);

+ 6 - 6
src/request/api/account.api.ts

@@ -69,12 +69,12 @@ export function getMenusMethod(account: AccountModel) {
   return request.Get<AccountModel, any[]>(`/system/menu/getRouters`, {
     headers: { Authorization: account.token },
     transform(data) {
-      data[5].children.push(
-        {
-          path: 'afterSale',
-          meta: { title: '售后' },
-        },
-      );
+      // data[5].children.push(
+      //   {
+      //     path: 'afterSale',
+      //     meta: { title: '售后' },
+      //   },
+      // );
       //   console.log(data, 'push之后的data', transformMenus(data));
       return { ...account, menus: transformMenus(data) };
     },

+ 58 - 1
src/request/api/order.api.ts

@@ -3,7 +3,8 @@ import type {
   OrderQuery,
   OrderModel, OrderLiaisonListModel, ShipmentModel, ShipmentQuery, 
   PieOrderCountModel, RevenueSharingDetailModel, RevenueSharingDetailQuery,
-   EvaluateDetailModel, ApplyRecordModel,LogisticsModel
+   EvaluateDetailModel, ApplyRecordModel,LogisticsModel,
+   AfterSaleModel,AfterSaleNegotiateModel,AfterSaleAuditModel
 } from '@/model/order.model';
 import request from '@/request/alova';
 
@@ -204,3 +205,59 @@ export function getLogisticsMethod(id: string) {
     }
   );
 } 
+
+// 获取售后分页列表
+export function getAfterSaleMethod(page: number, size: number, query?: AfterSaleQuery) {
+  return request.Post<List<AfterSaleModel>>(
+    '/fdhb-pc/patientCrManage/pageAftersale',
+    query ?? {},
+    {
+      hitSource: /order$/, // 匹配失效源
+      params: { pageNum: page, pageSize: size },
+    }
+  );
+}
+
+// 售后审核
+export function getAftersaleReviewMethod(data: AfterSaleAuditModel) {
+  return request.Post(
+    '/fdhb-pc/patientCrManage/aftersaleReview',
+    data,
+    {
+      hitSource: /order$/,
+    }
+  );
+}
+
+// 售后确定金额
+export function getConfirmAmountMethod(id: number, amount: number) {
+  return request.Post(
+    `/fdhb-pc/patientCrManage/confirmAmount/${id}/${amount}`,
+    {},
+    {
+      hitSource: /order$/,
+    }
+  );
+}
+
+// 售后详情 id患者调理方案售后ID
+export function getAfterSaleDetailMethod(id: number) {
+  return request.Post<AfterSaleModel>(
+    `/fdhb-pc/patientCrManage/detailAftersale/${id}`,
+    {},
+    {
+      hitSource: /order$/,
+    }
+  );
+}
+
+// 售后协商历史 id患者调理方案售后ID
+export function getAftersaleLogMethod(id: number) {
+  return request.Post<AfterSaleNegotiateModel[]>(
+    `/fdhb-pc/patientCrManage/getAftersaleLog/${id}`,
+    {},
+    {
+      hitSource: /order$/,
+    }
+  );
+}