Browse Source

售后列表,退换货功能

lxf 1 year ago
parent
commit
a6d2b6c720

+ 11 - 1
src/components/byTable/index.vue

@@ -117,7 +117,9 @@
         :row-style="{ height: '35px' }"
         header-row-class-name="tableHeader"
         :default-expand-all="defaultExpandAll || false"
-        :cell-class-name="cellClassName">
+        :cell-class-name="cellClassName"
+        :span-method="spanMethod"
+        :border="borderStatus">
         <el-table-column
           v-for="(item, index) in config"
           :key="index"
@@ -260,6 +262,14 @@ export default defineComponent({
       type: Function,
       default() {},
     },
+    spanMethod: {
+      type: Function,
+      default() {},
+    },
+    borderStatus: {
+      type: Boolean,
+      default: false,
+    },
   },
   setup(props) {
     const { proxy } = getCurrentInstance();

+ 14 - 0
src/router/index.js

@@ -216,6 +216,20 @@ export const constantRoutes = [
       },
     ],
   },
+  {
+    path: "/order/after-sale/initiate",
+    component: Layout,
+    redirect: "/order/after-sale/initiate",
+    children: [
+      {
+        path: "/order/after-sale/initiate",
+        name: "initiate",
+        component: () => import(/* webpackChunkName: "page" */ "@/views/group/order/after-sale/initiate.vue"),
+        props: true,
+        meta: { title: "售后" },
+      },
+    ],
+  },
 ];
 // 动态路由,基于用户权限动态去加载
 export const dynamicRoutes = [

+ 193 - 94
src/views/group/order/after-sale/index.vue

@@ -1,27 +1,22 @@
 <template>
-  <div>
-    <el-card class="box-card">
-      <!-- <byTable
-        :source="sourceList.data"
-        :pagination="sourceList.pagination"
-        :config="config"
-        :loading="loading"
-        highlight-current-row
-        @get-list="getList"
-        @clickReset="clickReset">
-        <template #code="{ item }">
-          <div>
-            <a style="color: #409eff; cursor: pointer; word-break: break-all" @click="clickCode(item)">{{ item.code }}</a>
-          </div>
-        </template>
-      </byTable> -->
-    </el-card>
-  </div>
+  <el-card class="box-card">
+    <byTable
+      :source="sourceList.data"
+      :pagination="sourceList.pagination"
+      :config="config"
+      :loading="loading"
+      :searchConfig="searchConfig"
+      highlight-current-row
+      @get-list="getList"
+      @clickReset="clickReset"
+      :spanMethod="spanMethod"
+      :borderStatus="true">
+    </byTable>
+  </el-card>
 </template>
 
 <script setup>
 import byTable from "/src/components/byTable/index";
-import { ElMessage } from "element-plus";
 
 const { proxy } = getCurrentInstance();
 const departmentList = ref([{ dictKey: "0", dictValue: "胜德体育" }]);
@@ -31,40 +26,96 @@ const sourceList = ref({
     total: 0,
     pageNum: 1,
     pageSize: 10,
-    departmentId: "",
     code: "",
-    wlnCode: "",
-    exception: "1",
+    orderCode: "",
+    orderWlnCode: "",
+    departmentId: "",
   },
 });
 const loading = ref(false);
+const searchConfig = computed(() => {
+  return [
+    {
+      type: "input",
+      prop: "code",
+      label: "售后单号",
+    },
+    {
+      type: "input",
+      prop: "orderCode",
+      label: "订单号",
+    },
+    {
+      type: "input",
+      prop: "orderWlnCode",
+      label: "万里牛单号",
+    },
+    {
+      type: "select",
+      prop: "departmentId",
+      data: departmentList.value,
+      label: "事业部",
+    },
+  ];
+});
 const config = computed(() => {
   return [
     {
       attrs: {
-        label: "事业部",
-        prop: "departmentName",
+        label: "售后库单号",
+        prop: "code",
       },
     },
     {
       attrs: {
-        label: "订单号",
-        slot: "code",
+        label: "售后订单",
+        prop: "orderCode",
       },
     },
     {
       attrs: {
         label: "万里牛单号",
-        prop: "wlnCode",
+        prop: "orderWlnCode",
       },
     },
     {
       attrs: {
-        label: "订单状态",
-        prop: "status",
+        label: "退货事业部",
+        prop: "departmentName",
       },
-      render(val) {
-        return proxy.dictKeyValue(val, proxy.useUserStore().allDict["order_status"]);
+    },
+    {
+      attrs: {
+        label: "SKU品号",
+        prop: "skuSpecCode",
+      },
+    },
+    {
+      attrs: {
+        label: "数量",
+        prop: "quantity",
+        width: 100,
+      },
+    },
+    {
+      attrs: {
+        label: "退货金额",
+        prop: "returnAmount",
+        width: 120,
+      },
+    },
+    {
+      attrs: {
+        label: "售后时间",
+        prop: "createTime",
+        align: "center",
+        width: 160,
+      },
+    },
+    {
+      attrs: {
+        label: "操作人",
+        prop: "createUserName",
       },
     },
     {
@@ -76,73 +127,121 @@ const config = computed(() => {
       },
       renderHTML(row) {
         return [
-          // {
-          //   attrs: {
-          //     label: "重新同步",
-          //     type: "primary",
-          //     text: true,
-          //   },
-          //   el: "button",
-          //   click() {
-          //     clickSynchronization(row);
-          //   },
-          // },
+          {
+            attrs: {
+              label: "查看详情",
+              type: "primary",
+              text: true,
+            },
+            el: "button",
+            click() {
+              clickDetail(row);
+            },
+          },
         ];
       },
     },
   ];
 });
-// const getDemandData = () => {
-//   proxy.post("/department/page", { pageNum: 1, pageSize: 999 }).then((res) => {
-//     if (res.rows && res.rows.length > 0) {
-//       departmentList.value = departmentList.value.concat(
-//         res.rows.map((item) => {
-//           return {
-//             dictKey: item.id,
-//             dictValue: item.name,
-//           };
-//         })
-//       );
-//     }
-//   });
-// };
-// getDemandData();
-// const getList = async (req, status) => {
-//   if (status) {
-//     sourceList.value.pagination = {
-//       pageNum: sourceList.value.pagination.pageNum,
-//       pageSize: sourceList.value.pagination.pageSize,
-//       exception: "1",
-//     };
-//   } else {
-//     sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
-//   }
-//   loading.value = true;
-//   proxy.post("/orderInfo/deletedOrderPage", sourceList.value.pagination).then((res) => {
-//     sourceList.value.data = res.rows;
-//     sourceList.value.pagination.total = res.total;
-//     setTimeout(() => {
-//       loading.value = false;
-//     }, 200);
-//   });
-// };
-// getList();
-// const clickReset = () => {
-//   getList("", true);
-// };
+const getDemandData = () => {
+  proxy.post("/department/page", { pageNum: 1, pageSize: 999 }).then((res) => {
+    if (res.rows && res.rows.length > 0) {
+      departmentList.value = departmentList.value.concat(
+        res.rows.map((item) => {
+          return {
+            dictKey: item.id,
+            dictValue: item.name,
+          };
+        })
+      );
+    }
+  });
+};
+getDemandData();
+const mergeRow = (list) => {
+  for (let field in list[0]) {
+    let k = 0;
+    let i = 0;
+    while (k < list.length) {
+      list[k][field + "Span"] = 1;
+      list[k][field + "Dis"] = false;
+      for (i = k + 1; i <= list.length - 1; i++) {
+        if (list[k][field] === list[i][field] && list[k].id === list[i].id) {
+          list[k][field + "Span"]++;
+          list[k][field + "Dis"] = false;
+          list[i][field + "Span"] = 1;
+          list[i][field + "Dis"] = true;
+        } else {
+          break;
+        }
+      }
+      k = i;
+    }
+  }
+  return list;
+};
+const getList = async (req, status) => {
+  if (status) {
+    sourceList.value.pagination = {
+      pageNum: sourceList.value.pagination.pageNum,
+      pageSize: sourceList.value.pagination.pageSize,
+    };
+  } else {
+    sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
+  }
+  loading.value = true;
+  proxy.post("/orderExchange/page", sourceList.value.pagination).then((res) => {
+    let list = [];
+    for (let i = 0; i < res.rows.length; i++) {
+      if (res.rows[i].orderExchangeDetailList && res.rows[i].orderExchangeDetailList.length > 0) {
+        for (let j = 0; j < res.rows[i].orderExchangeDetailList.length; j++) {
+          list.push({
+            id: res.rows[i].id,
+            code: res.rows[i].code,
+            orderCode: res.rows[i].orderCode,
+            orderWlnCode: res.rows[i].orderWlnCode,
+            departmentName: res.rows[i].departmentName,
+            skuSpecCode: res.rows[i].orderExchangeDetailList[j].skuSpecCode,
+            quantity: res.rows[i].orderExchangeDetailList[j].quantity,
+            returnAmount: res.rows[i].orderExchangeDetailList[j].returnAmount,
+            createTime: res.rows[i].createTime,
+            createUserName: res.rows[i].createUserName,
+          });
+        }
+      }
+    }
+    sourceList.value.data = Object.freeze(mergeRow(list));
+    sourceList.value.pagination.total = res.total;
+    setTimeout(() => {
+      loading.value = false;
+    }, 200);
+  });
+};
+getList();
+const clickReset = () => {
+  getList("", true);
+};
+const clickDetail = (row) => {
+  proxy.$router.replace({
+    path: "/order/after-sale/initiate",
+    query: {
+      id: row.id,
+      random: proxy.random(),
+    },
+  });
+};
+const spanMethod = ({ rowIndex, columnIndex }) => {
+  if ([0, 1, 2, 3, 7, 8, 9].includes(columnIndex)) {
+    let spanName = ["code", "orderCode", "orderWlnCode", "departmentName", "", "", "", "createTime", "createUserName", "id"];
+    const row1 = sourceList.value.data[rowIndex][spanName[columnIndex] + "Span"];
+    const colspan = sourceList.value.data[rowIndex][spanName[columnIndex] + "Dis"] ? 0 : 1;
+    const rowspan = colspan === 1 ? row1 : 0;
+    return {
+      rowspan: rowspan,
+      colspan: colspan,
+    };
+  }
+};
 </script>
 
-<style lang="scss" scoped>
-::v-deep(.el-input-number .el-input__inner) {
-  text-align: left;
-}
-:deep(.el-dialog) {
-  margin-top: 10px !important;
-  margin-bottom: 10px !important;
-}
-.select-card {
-  height: calc(100vh - 184px);
-  overflow-y: auto;
-  overflow-x: hidden;
-}
-</style>
+<style lang="scss" scoped></style>

+ 317 - 0
src/views/group/order/after-sale/initiate.vue

@@ -0,0 +1,317 @@
+<template>
+  <el-card class="box-card">
+    <div style="padding: 8px; text-align: center" v-if="orderDetail.code || orderDetail.wlnCode">
+      <span style="font-size: 18px; font-weight: 700">{{ orderDetail.code }} </span>
+      <span style="font-size: 18px; font-weight: 700" v-if="orderDetail.wlnCode"> ({{ orderDetail.wlnCode }})</span>
+    </div>
+    <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
+      <template #type>
+        <div style="width: 100%">
+          <span v-if="formData.data.type == 1">退款退货</span>
+          <span v-else-if="formData.data.type == 2">换货</span>
+        </div>
+      </template>
+      <template #img>
+        <div style="width: 100%">
+          <el-upload
+            v-model:file-list="fileList"
+            action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
+            list-type="picture-card"
+            multiple
+            :data="uploadData"
+            :before-upload="beforeUpload"
+            :on-preview="onPreview">
+            <el-icon><Plus /></el-icon>
+          </el-upload>
+        </div>
+      </template>
+      <template #orderExchangeDetailList>
+        <div style="width: 100%; padding: 0 1vw">
+          <el-table :data="formData.data.orderExchangeDetailList" :row-style="{ height: '35px' }" header-row-class-name="tableHeader">
+            <el-table-column label="SKU品号" prop="skuSpecCode" width="160" />
+            <el-table-column label="SKU品名" prop="skuSpecName" min-width="240" />
+            <el-table-column label="购买数量" prop="buyQuantity" width="120" />
+            <el-table-column label="售后数量" width="160">
+              <template #default="{ row, $index }">
+                <el-form-item :prop="'orderExchangeDetailList.' + $index + '.quantity'" :rules="rules.quantity" :inline-message="true" style="width: 100%">
+                  <el-input-number
+                    onmousewheel="return false;"
+                    v-model="row.quantity"
+                    placeholder="净重"
+                    style="width: 100%"
+                    :controls="false"
+                    :min="0"
+                    :precision="0" />
+                </el-form-item>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </template>
+      <template #deliveryAddress>
+        <div style="width: 100%">
+          <el-row>
+            <el-col :span="3">
+              <el-form-item label-width="0" prop="province" style="width: 100%">
+                <el-input v-model="formData.data.province" placeholder="请输入省" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="3">
+              <el-form-item label-width="0" prop="city" style="width: 100%">
+                <el-input v-model="formData.data.city" placeholder="请输入市" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="3">
+              <el-form-item label-width="0" prop="county" style="width: 100%">
+                <el-input v-model="formData.data.county" placeholder="请输入区/县" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="11">
+              <el-form-item label-width="0" prop="detailedAddress" style="width: 100%">
+                <el-input v-model="formData.data.detailedAddress" placeholder="请输入详细地址" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="4">
+              <el-form-item label-width="0" prop="postcode" style="width: 100%">
+                <el-input v-model="formData.data.postcode" placeholder="请输入邮编" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </template>
+      <template #consignee>
+        <div style="width: 100%">
+          <el-row>
+            <el-col :span="6">
+              <el-form-item label-width="0" prop="consignee" style="width: 100%">
+                <el-input v-model="formData.data.consignee" placeholder="请输入联系人" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label-width="0" prop="consigneeNumber" style="width: 100%">
+                <el-input v-model="formData.data.consigneeNumber" placeholder="请输入联系电话" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </div>
+      </template>
+    </byForm>
+    <div style="text-align: center; margin: 10px" v-if="!formOption.disabled">
+      <el-button @click="clickCancel()" size="large">取 消</el-button>
+      <el-button type="primary" @click="submitForm()" size="large" v-preReClick>提 交</el-button>
+    </div>
+  </el-card>
+</template>
+
+<script setup>
+import { useRoute, useRouter } from "vue-router";
+import byForm from "/src/components/byForm/index";
+import useTagsViewStore from "/src/store/modules/tagsView";
+import { ElMessage } from "element-plus";
+
+const { proxy } = getCurrentInstance();
+const route = useRoute();
+const router = useRouter();
+const warehouseList = ref([]);
+const formOption = reactive({
+  inline: true,
+  labelWidth: "120px",
+  itemWidth: 100,
+  rules: [],
+  labelPosition: "right",
+  disabled: false,
+});
+const formData = reactive({
+  data: {
+    type: "",
+    orderExchangeDetailList: [],
+    fileList: [],
+  },
+});
+const formConfig = computed(() => {
+  return [
+    {
+      type: "title",
+      title: "售后详情",
+      label: "",
+    },
+    {
+      type: "slot",
+      slotName: "type",
+      label: "售后处理",
+      itemWidth: 51,
+    },
+    {
+      type: "select",
+      label: "售后入仓",
+      prop: "warehouseId",
+      data: warehouseList.value,
+      itemWidth: 51,
+    },
+    {
+      type: "input",
+      prop: "reason",
+      label: "售后原因",
+      itemType: "text",
+      itemWidth: 51,
+    },
+    {
+      type: "input",
+      prop: "remark",
+      label: "备注",
+      itemType: "textarea",
+      itemWidth: 51,
+    },
+    {
+      type: "slot",
+      slotName: "img",
+      label: "商品图片",
+    },
+    {
+      type: "title",
+      title: "售后商品",
+      label: "",
+    },
+    {
+      type: "slot",
+      slotName: "orderExchangeDetailList",
+    },
+    {
+      type: "title",
+      title: "地址",
+      label: "",
+    },
+    {
+      type: "slot",
+      slotName: "deliveryAddress",
+      label: "收货地址",
+    },
+    {
+      type: "slot",
+      slotName: "consignee",
+      label: "收货人",
+    },
+  ];
+});
+const rules = ref({
+  warehouseId: [{ required: true, message: "请选择仓库", trigger: "change" }],
+  quantity: [{ required: true, message: "请输入数量", trigger: "blur" }],
+  reason: [{ required: true, message: "请输入售后原因", trigger: "blur" }],
+  province: [{ required: true, message: "请输入省", trigger: "blur" }],
+  detailedAddress: [{ required: true, message: "请输入详细地址", trigger: "blur" }],
+  consignee: [{ required: true, message: "请输入联系人", trigger: "blur" }],
+  consigneeNumber: [{ required: true, message: "请输入联系电话", trigger: "blur" }],
+});
+const getDemandData = () => {
+  proxy.post("/warehouse/page", { pageNum: 1, pageSize: 999 }).then((res) => {
+    if (res.rows && res.rows.length > 0) {
+      warehouseList.value = res.rows
+        .map((item) => {
+          return {
+            dictKey: item.id,
+            dictValue: item.name,
+          };
+        })
+        .filter((item) => ["1684037357424099330", "1684037305712525313"].includes(item.dictKey));
+    }
+  });
+};
+getDemandData();
+const fileList = ref([]);
+const uploadData = ref({});
+const beforeUpload = async (file) => {
+  const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
+  uploadData.value = res.uploadBody;
+  file.id = res.id;
+  file.fileName = res.fileName;
+  file.fileUrl = res.fileUrl;
+  return true;
+};
+const onPreview = (file) => {
+  window.open(file.raw.fileUrl, "_blank");
+};
+const orderDetail = ref({});
+onMounted(() => {
+  if (route.query && route.query.orderInfoId) {
+    formData.data.orderInfoId = route.query.orderInfoId;
+    formData.data.type = route.query.type;
+    proxy.post("/orderInfo/detail", { id: route.query.orderInfoId }).then((res) => {
+      orderDetail.value = res;
+      if (res.orderSkuList && res.orderSkuList.length > 0) {
+        formData.data.orderExchangeDetailList = res.orderSkuList.map((item) => {
+          return {
+            orderSkuId: item.id,
+            skuSpecCode: item.code,
+            skuSpecName: item.name,
+            buyQuantity: item.quantity,
+            quantity: undefined,
+          };
+        });
+      } else {
+        formData.data.orderExchangeDetailList = [];
+      }
+    });
+  } else if (route.query && route.query.id) {
+    formOption.disabled = true;
+    proxy.post("/orderExchange/detail", { id: route.query.id }).then((res) => {
+      formData.data = res;
+      proxy.post("/orderInfo/detail", { id: formData.data.orderInfoId }).then((resOrder) => {
+        orderDetail.value = resOrder;
+      });
+      proxy.post("/fileInfo/getList", { businessIdList: [route.query.id] }).then((fileObj) => {
+        if (fileObj[route.query.id] && fileObj[route.query.id].length > 0) {
+          fileList.value = fileObj[route.query.id].map((item) => {
+            return {
+              raw: item,
+              name: item.fileName,
+              url: item.fileUrl,
+            };
+          });
+        }
+      });
+    });
+  }
+});
+const clickCancel = () => {
+  const useTagsStore = useTagsViewStore();
+  useTagsStore.delVisitedView(router.currentRoute.value);
+  if (route.query && route.query.orderInfoId) {
+    router.replace({
+      path: "/group/order/order-management",
+    });
+  } else {
+    router.replace({
+      path: "/group/order/after-sale",
+    });
+  }
+};
+const submitForm = () => {
+  proxy.$refs.submit.handleSubmit(() => {
+    if (formData.data.orderExchangeDetailList && formData.data.orderExchangeDetailList.length > 0) {
+      if (fileList.value && fileList.value.length > 0) {
+        formData.data.fileList = fileList.value.map((item) => {
+          return {
+            id: item.raw.id,
+            fileName: item.raw.fileName,
+            fileUrl: item.raw.fileUrl,
+          };
+        });
+      } else {
+        formData.data.fileList = [];
+      }
+      proxy.post("/orderExchange/add", formData.data).then(() => {
+        ElMessage({ message: "提交成功", type: "success" });
+        clickCancel();
+      });
+    } else {
+      return ElMessage("请添加售后商品");
+    }
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+::v-deep(.el-input-number .el-input__inner) {
+  text-align: left;
+}
+</style>

+ 18 - 5
src/views/group/order/management/index.vue

@@ -72,10 +72,10 @@
       </template>
     </el-dialog>
 
-    <el-dialog title="售后类型" v-if="openAfterSale" v-model="openAfterSale" width="50%">
+    <el-dialog title="售后类型" v-if="openAfterSale" v-model="openAfterSale" width="300px" style="margin-top: 20vh !important">
       <template #footer>
-        <el-button type="primary" @click="applyForAfterSale(1)" size="large">退 货</el-button>
-        <el-button @click="applyForAfterSale(2)" size="large">换 货</el-button>
+        <el-button type="primary" @click="applyForAfterSale(1)" size="large" v-preReClick>退 货</el-button>
+        <el-button @click="applyForAfterSale(2)" size="large" v-preReClick>换 货</el-button>
       </template>
     </el-dialog>
   </div>
@@ -627,9 +627,22 @@ const clickCopyWLNCode = () => {
     });
   });
 };
-
+const openAfterSale = ref(false);
+const rowData = ref({});
 const clickAfterSale = (row) => {
-  console.log(row);
+  rowData.value = row;
+  openAfterSale.value = true;
+};
+const applyForAfterSale = (type) => {
+  openAfterSale.value = false;
+  proxy.$router.replace({
+    path: "/order/after-sale/initiate",
+    query: {
+      orderInfoId: rowData.value.id,
+      type: type,
+      random: proxy.random(),
+    },
+  });
 };
 </script>
 

+ 11 - 11
src/views/production/warehouse/management/index.vue

@@ -107,17 +107,17 @@ const config = computed(() => {
               clickUpdate(row);
             },
           },
-          {
-            attrs: {
-              label: "删除",
-              type: "danger",
-              text: true,
-            },
-            el: "button",
-            click() {
-              clickDelete(row);
-            },
-          },
+          // {
+          //   attrs: {
+          //     label: "删除",
+          //     type: "danger",
+          //     text: true,
+          //   },
+          //   el: "button",
+          //   click() {
+          //     clickDelete(row);
+          //   },
+          // },
         ];
       },
     },