فهرست منبع

部分新功能,以及售后管理模块

cz 1 سال پیش
والد
کامیت
b8bd8a4f28

+ 10 - 2
src/api/login.js

@@ -1,7 +1,7 @@
 import request from '@/utils/request'
 
 // 登录方法
-export function login(username, password, code, uuid,tenantId) {
+export function login(username, password, code, uuid, tenantId) {
   const data = {
     username,
     password,
@@ -12,7 +12,7 @@ export function login(username, password, code, uuid,tenantId) {
     url: '/login',
     headers: {
       isToken: false,
-      tenantId:tenantId
+      tenantId: tenantId
     },
     method: 'post',
     data: data
@@ -57,4 +57,12 @@ export function getCodeImg() {
     method: 'get',
     timeout: 20000
   })
+}
+
+// 获取用户所有字典
+export function allDictMap() {
+  return request({
+    url: "/tenantDict/allDictMap",
+    method: "get",
+  });
 }

+ 5 - 0
src/assets/styles/index.scss

@@ -248,4 +248,9 @@ aside {
   border-radius: 10px !important;
   background-color: rgb(192, 192, 192) !important;
   /* 悬停时的滑块背景色 */
+}
+
+// 页面外层盒子类
+.pageIndexClass {
+  padding: 10px !important;
 }

+ 51 - 23
src/components/byForm/index.vue

@@ -23,7 +23,7 @@
           <el-input v-if="i.type == 'input'" v-model="formData[i.prop]" :placeholder="i.placeholder || $t('common.pleaseEnter')"
                     @input="(e) => commonsEmit(e, i)" @change="(e) => commonsEmitChange(e, i)" :type="i.itemType ? i.itemType : 'text'"
                     :disabled="i.disabled ? i.disabled : false" :max="i.max" :min="i.min" :maxlength="i.maxlength"
-                    :readonly="i.readonly ? i.readonly : false" :style="i.style" />
+                    :readonly="i.readonly ? i.readonly : false" :style="i.style?i.style:{width:'100%'}" />
           <el-input v-if="i.type == 'selectInput'" v-model="formData[i.prop]" :placeholder="i.placeholder || $t('common.pleaseEnter')"
                     @input="(e) => commonsEmit(e, i)" :type="i.itemType ? i.itemType : 'text'" :disabled="i.disabled ? i.disabled : false"
                     :max="i.max" :min="i.min" :maxlength="i.maxlength" :readonly="i.readonly ? i.readonly : false">
@@ -39,8 +39,9 @@
           <el-select v-model="formData[i.prop]" :multiple="i.multiple || false" v-else-if="i.type == 'select'"
                      :placeholder="i.placeholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)"
                      :disabled="i.disabled ? i.disabled : false" :clearable="i.clearable ? i.clearable : false"
-                     :filterable="i.filterable ? true : false" :style="i.style" :readonly="i.readonly ? i.readonly : false">
-            <el-option :label="j.title || j.name || j.label" :value="j.id || j.value" v-for="j in i.data" :key="j.id">
+                     :filterable="i.filterable ? true : false" :style="i.style?i.style:{width:'100%'}" :readonly="i.readonly ? i.readonly : false">
+            <el-option :label="j.dictValue || j.name || j.label" :value="j.dictKey || j.id || j.value" v-for="j in i.data"
+                       :key="j.dictKey || j.id || j.value">
             </el-option>
           </el-select>
           <el-tree-select v-model="formData[i.prop]" v-else-if="i.type == 'treeSelect'" :data="i.data" :readonly="i.readonly ? i.readonly : false"
@@ -49,7 +50,7 @@
               label: i.propsTreeLabel || 'label',
               children: i.propsTreeChildren || 'children',
             }" value-key="id" :placeholder="i.placeholder || $t('common.pleaseSelect')" :disabled="i.disabled ? i.disabled : false" check-strictly
-                          :style="i.style" />
+                          :style="i.style?i.style:{width:'100%'}" />
           <el-date-picker v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false" v-else-if="i.type == 'date'" :type="i.itemType"
                           :placeholder="i.placeholder || $t('common.pleaseSelectTime')" @change="(e) => commonsEmit(e, i)"
                           :disabled="i.disabled ? i.disabled : false" :format="i.format ? i.format : dateFormatInit(i.itemType)"
@@ -72,14 +73,14 @@
                            :placeholder="i.placeholder || $t('common.pleaseEnter')" @change="(e) => commonsEmit(e, i)"
                            :disabled="i.disabled ? i.disabled : false" :min="i.min ? i.min : 0" :max="i.max ? i.max : 9999999999"
                            :step="i.step ? i.step : 1" :precision="i.precision !== '' ? i.precision : 2"
-                           :controls="i.controls === false ? false : true" :style="i.style" onmousewheel="return false;">
+                           :controls="i.controls === false ? false : true" :style="i.style?i.style:{width:'100%'}" onmousewheel="return false;">
           </el-input-number>
           <el-tree v-else-if="i.type == 'tree'" :data="i.data" :props="i.props" :readonly="i.readonly ? i.readonly : false"
                    :show-checkbox="i.showCheckbox || true">
           </el-tree>
           <el-cascader v-else-if="i.type == 'cascader'" :options="i.data" :props="i.props" :readonly="i.readonly ? i.readonly : false"
                        :placeholder="i.placeholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)"
-                       :disabled="i.disabled ? i.disabled : false" :style="i.style">
+                       :disabled="i.disabled ? i.disabled : false" :style="i.style?i.style:{width:'100%'}">
           </el-cascader>
           <!-- <div class="form-title" v-else-if="i.type == 'title'">
             {{ i.title }}
@@ -96,12 +97,16 @@
             {{ i.slotName }}插槽占位符
           </slot>
           <div class="upload" v-else-if="i.type == 'upload'">
-            <el-upload v-model="formData[i.prop]" :action="uploadUrl" :data="uploadData" list-type="picture-card" :on-remove="handleRemove"
-                       :on-success="handleSuccess" :before-upload="handleBeforeUpload">
-              <!-- <el-icon class="el-icon--upload"><upload-filled /></el-icon> -->
-              <el-icon>
+            <el-upload :file-list="formData[i.prop]?formData[i.prop]:[]" multiple :action="uploadUrl" :data="uploadData"
+                       :list-type="i.listType ? i.listType : 'text'" :accept="i.accept?i.accept :''" :limit="i.limit?i.limit:3"
+                       :before-upload="(file)=>handleBeforeUpload(file,i)" :on-success="()=>handleSuccess(i)"
+                       :on-remove="(file)=>handleRemove(file,i)" :on-exceed="()=>handleExceed(i)" :on-preview="onPreviewFile">
+
+              <el-icon v-if="i.listType=='picture-card'">
                 <Plus />
               </el-icon>
+              <!-- <span v-else-if="formOption.disabled" style="color:#409EFF">已上传:</span> -->
+              <el-button type="primary" plain v-else>点击上传</el-button>
             </el-upload>
           </div>
           <div v-else-if="i.type == 'table'" class="by-form-table" style="width: 100%">
@@ -174,28 +179,51 @@ const formTableObj = {
 const fileList = ref([]);
 const fileListCopy = ref([]);
 const uploadData = ref({});
+const fileData = ref({});
 //文件上传相关方法
-const handleSuccess = (res, file, files) => {
+const handleSuccess = (item) => {
+  if (fileData.value && fileData.value.fileUrl) {
+    formData.value[item.prop].push({
+      id: fileData.value.id,
+      fileName: fileData.value.fileName,
+      name: fileData.value.fileName,
+      url: fileData.value.fileUrl,
+      fileUrl: fileData.value.fileUrl,
+    });
+  }
   emit("update:modelValue", formData.value);
 };
 
-const handleRemove = (file) => {
-  const index = fileListCopy.value.findIndex(
-    (x) => x.uid === file.uid || x.id === file.id
+const handleRemove = (file, item) => {
+  let index = formData.value[item.prop].findIndex(
+    (x) => x.id == file.id || x.id == file.raw.id
   );
-  fileListCopy.value.splice(index, 1);
+  if (index > -1) {
+    formData.value[item.prop].splice(index, 1);
+  }
+};
+
+const handleExceed = (item) => {
+  let limit = item.limit || 3;
+  return proxy.msgTip(`上传文件数量不可大于${limit}`, 2);
 };
 
-const handleBeforeUpload = async (file) => {
+const onPreviewFile = (file, a) => {
+  if (file && file.fileUrl) {
+    window.open(file.fileUrl, "_blank");
+  } else {
+    window.open(file.raw.fileUrl, "_blank");
+  }
+};
+
+const handleBeforeUpload = async (file, item) => {
+  fileData.value = {};
   const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
+  file.id = res.id;
+  file.fileUrl = res.fileUrl;
   uploadData.value = res.uploadBody;
-  fileListCopy.value.push({
-    id: res.id,
-    fileName: res.fileName,
-    path: res.fileUrl,
-    url: res.fileUrl,
-    uid: file.uid,
-  });
+  fileData.value = res;
+  return true;
 };
 
 const isInit = ref(false);

+ 43 - 13
src/components/byTable/index.vue

@@ -57,22 +57,15 @@
         </div>
         <div class="by-dropdown" v-for="(i, index) in selectConfigCopy" :key="i.prop" style="margin-right: 10px">
           <div class="by-dropdown-title">
-            {{
-              pagination[i.prop]
-                ? i.data.find((j) => j.value === pagination[i.prop])
-                  ? i.data.find((j) => j.value === pagination[i.prop]).label
-                  : i.label
-                : i.labelCopy
-            }}
-            <!-- {{ i.label || i.labelCopy }} -->
+            {{getSelectLabel(pagination[i.prop],i) || i.label || i.labelCopy}}
             <i style="margin-left: 5px" class="iconfont icon-iconm_xialan1"></i>
           </div>
           <ul class="by-dropdown-lists">
             <li @click="searchItemSelct('all', i, index)" v-if="i.isShowAll === false ? i.isShowAll : true" style="">
               {{ $t("common.all") }}
             </li>
-            <li v-for="j in i.data" :key="j.value" @click="searchItemSelct(j, i)" style="">
-              {{ j.label }}
+            <li v-for="j in i.data" :key="j.value || j.dictKey" @click="searchItemSelct(j, i)" style="">
+              {{ j.label || j.dictValue }}
             </li>
           </ul>
         </div>
@@ -415,18 +408,54 @@ export default defineComponent({
 
     const searchItemSelct = (item, i, index) => {
       if (item == "all") {
-        i.label = { ...props.selectConfig[index] }.labelCopy;
+        // i.label = { ...props.selectConfig[index] }.labelCopy;
+        i.label = props.selectConfig[index].labelCopy;
         proxy.$emit(
           "getList",
           Object.assign(props.filterParams, { [i.prop]: "" })
         );
+        if (i.fn) {
+          i.fn();
+        }
         return;
       }
-      i.label = item.label;
+      i.label = item.label || item.dictValue;
+      let value = null;
+      if (item.value !== "" && item.value != undefined) {
+        value = item.value;
+      } else if (item.dictKey !== "" && item.dictKey != undefined) {
+        value = item.dictKey;
+      } else {
+        console.log("过滤");
+      }
       proxy.$emit(
         "getList",
-        Object.assign(props.filterParams, { [i.prop]: item.value })
+        Object.assign(props.filterParams, {
+          [i.prop]: value,
+        })
       );
+      // 是否有函数
+      if (i.fn) {
+        i.fn();
+      }
+    };
+
+    const getSelectLabel = (value, item) => {
+      if (value !== "" && value != undefined) {
+        let current = item.data.find(
+          (x) => x.value == value || x.dictKey == value
+        );
+        if (current) {
+          if (current.label) {
+            return current.label;
+          }
+          if (current.dictValue) {
+            return current.dictValue;
+          }
+        }
+      } else {
+        return item.labelCopy;
+      }
     };
 
     const isSelectable = (row, index, item) => {
@@ -460,6 +489,7 @@ export default defineComponent({
       handleNativeClick,
       searchFn,
       searchItemSelct,
+      getSelectLabel,
       selectConfigCopy,
       isSelectable,
       retrievalModal,

+ 398 - 0
src/components/process/EHSD/AfterSales.vue

@@ -0,0 +1,398 @@
+<template>
+  <div style="width: 100%; padding: 0px 15px">
+    <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="formDom" v-loading="submitLoading">
+      <template #detail>
+        <div style="width:100%">
+          <el-button type="primary" @click="openMaterial = true" plain style="margin-bottom: 16px" v-if="!judgeStatus()">选择产品</el-button>
+          <el-table :data="formData.data.afterSalesDetailList" style="width: 100%; ">
+            <el-table-column prop="contractCode" label="订单号" width="130" />
+            <el-table-column label="图片" width="80" align="center">
+              <template #default="{ row }">
+                <div v-if="row.fileUrl">
+                  <img :src="row.fileUrl" class="pic" @click="onPicture(row.fileUrl)" />
+                </div>
+                <div v-else></div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="productCode" label="编码" width="100" />
+            <el-table-column prop="productName" label="名称" min-width="200" />
+            <el-table-column prop="productModel" label="尺寸 (cm)" width="120" />
+            <el-table-column prop="price" label="单价" width="90" />
+            <el-table-column prop="contractQuantity" label="数量" width="90" />
+            <el-table-column label="售后数量" width="120">
+              <template #default="{ row, $index }">
+                <div style="width: 100%">
+                  <el-form-item :prop="'afterSalesDetailList.' + $index + '.quantity'" :rules="rules.quantity" :inline-message="true"
+                                class="shrinkPadding">
+                    <el-input-number onmousewheel="return false;" v-model="row.quantity" placeholder="请输入" style="width: 100%" :precision="0"
+                                     :controls="false" :min="0" @change="totalAmount()" />
+                  </el-form-item>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="amount" label="小计" width="100" />
+            <el-table-column label="备注" min-width="150">
+              <template #default="{ row, $index }">
+                <div style="width: 100%">
+                  <el-form-item :prop="'afterSalesDetailList.' + $index + '.remark'" :rules="rules.remark" :inline-message="true"
+                                class="shrinkPadding">
+                    <el-input v-model="row.remark" placeholder="请输入" type="text"></el-input>
+                  </el-form-item>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="60" align="center" fixed="right" v-if="!judgeStatus()">
+              <template #default="{ row, $index }">
+                <el-button type="primary" link @click="handleRemove($index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+      </template>
+    </byForm>
+
+    <el-dialog v-model="openMaterial" title="选择订单产品" width="90%" append-to-body>
+      <SelectContractProduct @selectProduct="handleSelect"></SelectContractProduct>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="openMaterial = false">取消</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+  </div>
+
+</template>
+
+<script setup>
+import byForm from "@/components/byForm/index";
+import SelectContractProduct from "@/views/salesMange/afterSales/SelectContractProduct.vue";
+import { useRoute } from "vue-router";
+const { proxy } = getCurrentInstance();
+const route = useRoute();
+// 接收父组件的传值
+const props = defineProps({
+  queryData: Object,
+});
+const userList = ref([]);
+const afterSalesType = computed(
+  () => proxy.useUserStore().allDict["after_sales_type"]
+);
+const openMaterial = ref(false);
+const formData = reactive({
+  data: {
+    afterSalesDetailList: [],
+  },
+});
+const formOption = reactive({
+  inline: true,
+  labelWidth: 110,
+  itemWidth: 100,
+  rules: [],
+});
+const rules = ref({
+  type: [{ required: true, message: "请选择售后类型", trigger: "change" }],
+  reason: [{ required: true, message: "请输入售后原因", trigger: "blur" }],
+  remark: [{ required: true, message: "请输入售后说明", trigger: "blur" }],
+  dutyParty: [{ required: true, message: "请输入初判责任方", trigger: "blur" }],
+  contactName: [
+    { required: true, message: "请输入客户联系人", trigger: "blur" },
+  ],
+  contactTel: [
+    { required: true, message: "请输入客户联系电话", trigger: "blur" },
+  ],
+  // userId: [{ required: true, message: "请选择售后人员", trigger: "change" }],
+  quantity: [{ required: true, message: "请输入售后数量", trigger: "blur" }],
+  accountBank: [{ required: true, message: "请输入开户行", trigger: "blur" }],
+  accountName: [{ required: true, message: "请输入户名", trigger: "blur" }],
+  accountNumber: [{ required: true, message: "请输入账号", trigger: "blur" }],
+});
+const formConfig = computed(() => {
+  return [
+    {
+      type: "title",
+      title: "基本信息",
+      haveLine: false,
+    },
+    {
+      type: "select",
+      prop: "type",
+      label: "售后类型",
+      required: true,
+      itemWidth: 50,
+      data: afterSalesType.value,
+      disabled: false,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "reason",
+      label: "售后原因",
+      disabled: false,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "textarea",
+      prop: "remark",
+      label: "售后说明",
+      disabled: false,
+      itemWidth: 100,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "amount",
+      label: "售后金额",
+      disabled: true,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "dutyParty",
+      label: "初判责任方",
+      disabled: false,
+      itemWidth: 50,
+    },
+    // {
+    //   type: "select",
+    //   prop: "userId",
+    //   label: "售后人员",
+    //   itemWidth: 50,
+    //   filterable: true,
+    //   data: userList.value,
+    //   disabled: false,
+    // },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "contactName",
+      label: "客户联系人",
+      disabled: false,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "contactTel",
+      label: "联系电话",
+      disabled: false,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "accountBank",
+      label: "开户行",
+      required: true,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "accountName",
+      label: "户名",
+      required: true,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "accountNumber",
+      label: "账号",
+      required: true,
+      itemWidth: 50,
+    },
+    // {
+    //   type: "title",
+    //   title: "客户账户信息",
+    //   haveLine: true,
+    // },
+    {
+      type: "title",
+      title: "售后明细",
+      haveLine: true,
+    },
+    {
+      type: "slot",
+      slotName: "detail",
+      label: "",
+    },
+  ];
+});
+
+const formDom = ref(null);
+const judgeStatus = () => {
+  if (route.query.processType == 20 || route.query.processType == 10) {
+    return true;
+  }
+  if (props.queryData.recordList && props.queryData.recordList.length > 0) {
+    let data = props.queryData.recordList.filter(
+      (item) => item.status === 2 && item.nodeType !== 1
+    );
+    if (data && data.length > 0) {
+      return true;
+    }
+  }
+  return false;
+};
+
+const totalAmount = () => {
+  let money = 0;
+  if (
+    formData.data.afterSalesDetailList &&
+    formData.data.afterSalesDetailList.length > 0
+  ) {
+    for (let i = 0; i < formData.data.afterSalesDetailList.length; i++) {
+      formData.data.afterSalesDetailList[i].amount = parseFloat(
+        Number(formData.data.afterSalesDetailList[i].quantity) *
+          Number(formData.data.afterSalesDetailList[i].price)
+      ).toFixed(2);
+      money = parseFloat(
+        Number(money) + Number(formData.data.afterSalesDetailList[i].amount)
+      ).toFixed(2);
+    }
+  }
+  formData.data.amount = money;
+};
+
+const handleSubmit = async (isStag = false) => {
+  if (isStag) {
+    return true;
+  }
+  let flag = await formDom.value.handleSubmit(() => {});
+  if (flag) {
+    if (
+      formData.data.afterSalesDetailList &&
+      formData.data.afterSalesDetailList.length > 0
+    ) {
+      return true;
+    } else {
+      proxy.msgTip("请添加售后明细", 2);
+      return false;
+    }
+  } else {
+  }
+  return flag;
+};
+
+const handleSelect = (row) => {
+  if (
+    formData.data.afterSalesDetailList &&
+    formData.data.afterSalesDetailList.length == 0
+  ) {
+    formData.data.contractId = row.contractId;
+    formData.data.afterSalesDetailList.push({
+      contractProductId: row.id,
+      productId: row.productId,
+      contractCode: row.contractCode,
+      productName: row.productName,
+      productCode: row.productCode,
+      productModel: row.productModel,
+      fileUrl: row.fileUrl || "",
+      contractQuantity: row.quantity,
+      price: row.price,
+      quantity: null,
+      remark: "",
+      amount: "",
+    });
+    return proxy.msgTip("选择成功");
+  } else {
+    if (formData.data.contractId == row.contractId) {
+      let flag = formData.data.afterSalesDetailList.some(
+        (x) => x.productId == row.productId
+      );
+      if (!flag) {
+        formData.data.afterSalesDetailList.push({
+          contractProductId: row.id,
+          productId: row.productId,
+          contractCode: row.contractCode,
+          productName: row.productName,
+          productCode: row.productCode,
+          productModel: row.productModel,
+          fileUrl: row.fileUrl || "",
+          contractQuantity: row.quantity,
+          price: row.price,
+          quantity: null,
+          remark: "",
+          amount: "",
+        });
+        return proxy.msgTip("选择成功");
+      }
+      proxy.msgTip("该产品已选择", 2);
+    } else {
+      return proxy.msgTip("只能选同个订单的产品", 2);
+    }
+  }
+};
+
+const handleRemove = (index) => {
+  formData.data.afterSalesDetailList.splice(index, 1);
+};
+
+const getDict = () => {
+  proxy
+    .get("/tenantUser/list", {
+      pageNum: 1,
+      pageSize: 10000,
+      companyId: proxy.useUserStore().user.companyId,
+    })
+    .then((res) => {
+      userList.value = res.rows.map((item) => {
+        return {
+          label: item.nickName,
+          value: item.userId,
+        };
+      });
+    });
+};
+// getDict();
+
+const getAllData = (businessId) => {
+  if (businessId) {
+    proxy.post("/afterSales/detail", { id: businessId }).then((res) => {
+      res.afterSalesDetailList.map((x) => (x.contractCode = res.contractCode));
+      formData.data = res;
+      let productIds = formData.data.afterSalesDetailList.map(
+        (x) => x.productId
+      );
+      proxy.getFileData({
+        businessIdList: productIds,
+        data: formData.data.afterSalesDetailList,
+        att: "productId",
+        businessType: "0",
+        fileAtt: "fileList",
+        filePathAtt: "fileUrl",
+      });
+    });
+  }
+};
+
+onMounted(() => {
+  formOption.disabled = judgeStatus();
+  if (route.query.businessId) {
+    getAllData(route.query.businessId);
+  }
+});
+
+const getFormData = () => {
+  return proxy.deepClone(formData.data);
+};
+// 向父组件暴露
+defineExpose({
+  getFormData,
+  handleSubmit,
+});
+</script>
+
+<style lang="scss" scoped>
+// 全局img样式
+.pic {
+  object-fit: contain;
+  width: 50px;
+  height: 50px;
+  cursor: pointer;
+  vertical-align: middle;
+}
+</style>

+ 35 - 7
src/components/process/EHSD/Contract.vue

@@ -234,7 +234,7 @@
       <template #delivery>
         <div style="width: 100%">
           <el-row style="margin-top: 20px; width: 100%">
-            <el-col :span="6">
+            <!-- <el-col :span="6">
               <el-form-item label="报价有效期 (天)" prop="effective">
                 <el-input-number onmousewheel="return false;" v-model="formData.data.effective" placeholder="请输入有效期" style="width: 100%"
                                  :precision="0" :controls="false" :min="0" />
@@ -244,15 +244,15 @@
               <el-form-item label="交货期限" prop="deliveryTime">
                 <el-date-picker v-model="formData.data.deliveryTime" type="date" placeholder="请选择交货期限" value-format="YYYY-MM-DD" />
               </el-form-item>
-            </el-col>
-            <el-col :span="6">
+            </el-col> -->
+            <el-col :span="12">
               <el-form-item label="运输方式" prop="transportMethod">
                 <el-select v-model="formData.data.transportMethod" placeholder="请选择运输方式" style="width: 100%">
                   <el-option v-for="item in shippingMethod" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
-            <el-col :span="6">
+            <el-col :span="12">
               <el-form-item label="运输说明" prop="transportRemark">
                 <el-input v-model="formData.data.transportRemark" placeholder="请输入运输说明" />
               </el-form-item>
@@ -340,7 +340,7 @@
                         </template>
                         <template #reference>
                           <div style="margin-left:10px;cursor:pointer;position:relative;top:4px">
-                            <el-icon :size="20" color="#85c1a6">
+                            <el-icon :size="20" :color="getColor(row)">
                               <WarningFilled />
                             </el-icon>
                           </div>
@@ -531,7 +531,7 @@
 
         <div style="width: 100%">
           <el-row>
-            <el-col :span="10" style="border: 1px solid #ebeef5; padding: 10px">
+            <el-col :span="12" style="border: 1px solid #ebeef5; padding: 10px">
               <el-form-item label="出货日期" required>
                 <el-date-picker v-model="formData.data.shipmentTime" type="date" placeholder="请选择出货日期" value-format="YYYY-MM-DD" />
               </el-form-item>
@@ -558,7 +558,7 @@
                 <el-button type="primary" @click="handleAddShipment">添加</el-button>
               </div>
             </el-col>
-            <el-col :span="14">
+            <el-col :span="12">
               <div style="padding: 10px; margin-top: 77px">
                 <el-table :data="formData.data.contractShipmentList" :span-method="objectSpanMethod">
                   <el-table-column prop="shipmentTime" label="出货日期" width="100">
@@ -1298,6 +1298,13 @@ const changeProductPrice = () => {
               iele.salePrice = res[key].price;
               iele.currency = res[key].currency;
               iele.saleCostPrice = res[key].costPrice;
+              iele.contractProductListCopy = res[key].contractProductList.map(
+                (x) => ({
+                  createTime: x.createTime || "",
+                  price: x.price,
+                  currency: x.currency,
+                })
+              );
               iele.contractProductList = res[key].contractProductList
                 .map((x) => ({
                   createTime: x.createTime || "",
@@ -2268,6 +2275,27 @@ const objectSpanMethod = ({ rowIndex, columnIndex }) => {
     };
   }
 };
+
+const getColor = (row) => {
+  if (row.contractProductListCopy && row.contractProductListCopy.length > 0) {
+    let arr = row.contractProductListCopy.map((x) => x.price);
+    let minPrice = Math.min(...arr);
+    let maxPrice = Math.max(...arr);
+    if (row.price && row.price != 0) {
+      if (row.price > maxPrice) {
+        return "#ff041c";
+      } else if (row.price < minPrice) {
+        return "#edd067";
+      } else {
+        return "#85c1a6";
+      }
+    } else {
+      return "#85c1a6";
+    }
+  } else {
+    return "#85c1a6";
+  }
+};
 </script>
 
 <style lang="scss" scoped>

+ 35 - 7
src/components/process/EHSD/ContractChange.vue

@@ -227,7 +227,7 @@
       <template #delivery>
         <div style="width: 100%">
           <el-row style="margin-top: 20px; width: 100%">
-            <el-col :span="6" v-show="showAllData">
+            <!-- <el-col :span="6" v-show="showAllData">
               <el-form-item label="报价有效期 (天)" prop="effective">
                 <el-input-number onmousewheel="return false;" v-model="formData.data.effective" placeholder="请输入有效期" style="width: 100%"
                                  :precision="0" :controls="false" :min="0" />
@@ -237,15 +237,15 @@
               <el-form-item label="交货期限" prop="deliveryTime">
                 <el-date-picker v-model="formData.data.deliveryTime" type="date" placeholder="请选择交货期限" value-format="YYYY-MM-DD" />
               </el-form-item>
-            </el-col>
-            <el-col :span="6">
+            </el-col> -->
+            <el-col :span="12">
               <el-form-item label="运输方式" prop="transportMethod">
                 <el-select v-model="formData.data.transportMethod" placeholder="请选择运输方式" style="width: 100%">
                   <el-option v-for="item in shippingMethod" :key="item.value" :label="item.label" :value="item.value" />
                 </el-select>
               </el-form-item>
             </el-col>
-            <el-col :span="6">
+            <el-col :span="12">
               <el-form-item label="运输说明" prop="transportRemark">
                 <el-input v-model="formData.data.transportRemark" placeholder="请输入运输说明" />
               </el-form-item>
@@ -334,7 +334,7 @@
                         </template>
                         <template #reference>
                           <div style="margin-left:10px;cursor:pointer;position:relative;top:4px">
-                            <el-icon :size="20" color="#85c1a6">
+                            <el-icon :size="20" :color="getColor(row)">
                               <WarningFilled />
                             </el-icon>
                           </div>
@@ -523,7 +523,7 @@
         </div> -->
         <div style="width: 100%">
           <el-row>
-            <el-col :span="10" style="border: 1px solid #ebeef5; padding: 10px">
+            <el-col :span="12" style="border: 1px solid #ebeef5; padding: 10px">
               <el-form-item label="出货日期" required>
                 <el-date-picker v-model="formData.data.shipmentTime" type="date" placeholder="请选择出货日期" value-format="YYYY-MM-DD" />
               </el-form-item>
@@ -550,7 +550,7 @@
                 <el-button type="primary" @click="handleAddShipment">添加</el-button>
               </div>
             </el-col>
-            <el-col :span="14">
+            <el-col :span="12">
               <div style="padding: 10px; margin-top: 77px">
                 <el-table :data="formData.data.contractShipmentList" :span-method="objectSpanMethod">
                   <el-table-column prop="shipmentTime" label="出货日期" width="100">
@@ -1328,6 +1328,13 @@ const changeProductPrice = () => {
               iele.salePrice = res[key].price;
               iele.currency = res[key].currency;
               iele.saleCostPrice = res[key].costPrice;
+              iele.contractProductListCopy = res[key].contractProductList.map(
+                (x) => ({
+                  createTime: x.createTime || "",
+                  price: x.price,
+                  currency: x.currency,
+                })
+              );
               iele.contractProductList = res[key].contractProductList
                 .map((x) => ({
                   createTime: x.createTime || "",
@@ -2025,6 +2032,27 @@ const objectSpanMethod = ({ rowIndex, columnIndex }) => {
     };
   }
 };
+
+const getColor = (row) => {
+  if (row.contractProductListCopy && row.contractProductListCopy.length > 0) {
+    let arr = row.contractProductListCopy.map((x) => x.price);
+    let minPrice = Math.min(...arr);
+    let maxPrice = Math.max(...arr);
+    if (row.price && row.price != 0) {
+      if (row.price > maxPrice) {
+        return "#ff041c";
+      } else if (row.price < minPrice) {
+        return "#edd067";
+      } else {
+        return "#85c1a6";
+      }
+    } else {
+      return "#85c1a6";
+    }
+  } else {
+    return "#85c1a6";
+  }
+};
 </script>
 
 <style lang="scss" scoped>

+ 287 - 12
src/components/process/EHSD/Purchase.vue

@@ -309,7 +309,7 @@
                                   </template>
                                   <template #reference>
                                     <div style="margin-left:10px;cursor:pointer;position:relative;top:4px">
-                                      <el-icon :size="20" color="#85c1a6">
+                                      <el-icon :size="20" :color="getColor(row)">
                                         <WarningFilled />
                                       </el-icon>
                                     </div>
@@ -485,6 +485,60 @@
           </el-table>
         </div>
       </template>
+      <template #budget>
+        <div style="width:100%">
+          <el-form ref="ruleFormRef" :model="formData.data" :rules="rules" label-width="100px" class="demo-ruleForm">
+            <el-row style="margin-bottom:20px">
+              <el-col :span="6">
+                <el-form-item prop="trailerFee" label="拖车费">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.trailerFee" placeholder="请输入" style="width: 100%"
+                                   :precision="2" :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item prop="customsFee" label="报关费">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.customsFee" placeholder="请输入" style="width: 100%"
+                                   :precision="2" :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item prop="agencyFee" label="代理费">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.agencyFee" placeholder="请输入" style="width: 100%" :precision="2"
+                                   :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item prop="portMixedFee" label="港杂费">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.portMixedFee" placeholder="请输入" style="width: 100%"
+                                   :precision="2" :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row>
+              <el-col :span="6">
+                <el-form-item prop="inspectionRedPack" label="验货红包">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.inspectionRedPack" placeholder="请输入" style="width: 100%"
+                                   :precision="2" :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item prop="commission" label="佣金">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.commission" placeholder="请输入" style="width: 100%"
+                                   :precision="2" :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item prop="other" label="其他">
+                  <el-input-number onmousewheel="return false;" v-model="formData.data.other" placeholder="请输入" style="width: 100%" :precision="2"
+                                   :controls="false" :min="0" />
+                </el-form-item>
+              </el-col>
+
+            </el-row>
+
+          </el-form>
+        </div>
+      </template>
     </byForm>
 
     <el-dialog v-if="openMaterialCompany" v-model="openMaterialCompany" title="物料选择" width="90%" append-to-body>
@@ -580,6 +634,7 @@ const productType = ref([
     value: 2,
   },
 ]);
+
 const judgeStatus = () => {
   if (route.query.processType == 20 || route.query.processType == 10) {
     return true;
@@ -594,6 +649,30 @@ const judgeStatus = () => {
   }
   return false;
 };
+
+const isFinance = () => {
+  if (
+    route.query.processType &&
+    route.query.processType == 10 &&
+    formData.data.dataResource &&
+    formData.data.dataResource == 1 &&
+    props.queryData.recordList &&
+    props.queryData.recordList.length > 0
+  ) {
+    let data = props.queryData.recordList.filter(
+      (item) => item.status === 2 && item.nodeType !== 1
+    );
+    if (data && data.length > 0) {
+      if (data[0] && data[0].nodeName.includes("财务")) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+  }
+  return false;
+};
+
 const formOption = reactive({
   inline: true,
   labelWidth: 100,
@@ -713,6 +792,110 @@ const formConfig = computed(() => {
       slotName: "shipment",
       label: "",
     },
+    {
+      type: "title",
+      title: "预算",
+      haveLine: true,
+      isShow: isFinance(),
+    },
+    {
+      type: "slot",
+      slotName: "budget",
+      label: "",
+      isShow: isFinance(),
+    },
+    // {
+    //   type: "number",
+    //   label: "拖车费",
+    //   prop: "trailerFee",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   disabled: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
+    // {
+    //   type: "number",
+    //   label: "报关费",
+    //   prop: "customsFee",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
+    // {
+    //   type: "number",
+    //   label: "代理费",
+    //   prop: "agencyFee",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
+    // {
+    //   type: "number",
+    //   label: "港杂费",
+    //   prop: "portMixedFee",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
+    // {
+    //   type: "number",
+    //   label: "验货红包",
+    //   prop: "inspectionRedPack",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
+    // {
+    //   type: "number",
+    //   label: "佣金",
+    //   prop: "commission",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
+    // {
+    //   type: "number",
+    //   label: "其他",
+    //   prop: "other",
+    //   itemWidth: 25,
+    //   precision: 2,
+    //   min: 0,
+    //   controls: false,
+    //   style: {
+    //     width: "100%",
+    //   },
+    //   isShow: isFinance(),
+    // },
   ];
 });
 const rules = ref({
@@ -754,7 +937,16 @@ const rules = ref({
   contractTemplateId: [
     { required: true, message: "请选择合同模板", trigger: "change" },
   ],
-  remark: [{ required: true, message: "请输入条款内容", trigger: "change" }],
+  remark: [{ required: true, message: "请输入条款内容", trigger: "blur" }],
+  trailerFee: [{ required: true, message: "请输入拖车费", trigger: "blur" }],
+  customsFee: [{ required: true, message: "请输入报关费", trigger: "blur" }],
+  agencyFee: [{ required: true, message: "请输入代理费", trigger: "blur" }],
+  portMixedFee: [{ required: true, message: "请输入港杂费", trigger: "blur" }],
+  inspectionRedPack: [
+    { required: true, message: "请输入验货红包", trigger: "blur" },
+  ],
+  commission: [{ required: true, message: "请输入佣金", trigger: "blur" }],
+  other: [{ required: true, message: "请输入其他", trigger: "blur" }],
 });
 const getDict = () => {
   proxy.getDictOne(["invoice_type", "funds_payment_method"]).then((res) => {
@@ -979,6 +1171,12 @@ const changeProductPrice = () => {
             const jele = iele.purchaseProductMountingsList[j];
             for (const key in resOne) {
               if (jele.productId == key) {
+                jele.purchaseProductPriceListCopy = resOne[
+                  key
+                ].purchaseProductList.map((x) => ({
+                  createTime: x.createTime,
+                  price: x.price,
+                }));
                 jele.purchaseProductPriceList = resOne[key].purchaseProductList
                   .map((x) => ({
                     createTime: x.createTime,
@@ -1234,6 +1432,7 @@ const querySearch = (queryString, callback) => {
     }
   });
 };
+const ruleFormRef = ref(null);
 const handleSubmit = async () => {
   let status = await submit.value.handleSubmit(() => {});
   if (status) {
@@ -1273,6 +1472,33 @@ const handleSubmit = async () => {
     //     }
     //   }
     // }
+    if (isFinance()) {
+      let vaild = await ruleFormRef.value.validate();
+      if (vaild) {
+        let data = {
+          contractId: formData.data.dataResourceId,
+          trailerFee: formData.data.trailerFee,
+          customsFee: formData.data.customsFee,
+          agencyFee: formData.data.agencyFee,
+          portMixedFee: formData.data.portMixedFee,
+          inspectionRedPack: formData.data.inspectionRedPack,
+          commission: formData.data.commission,
+          other: formData.data.other,
+          trailerFeeCurrency: "CNY",
+          customsFeeCurrency: "CNY",
+          agencyFeeCurrency: "CNY",
+          portMixedFeeCurrency: "CNY",
+          inspectionRedPackCurrency: "CNY",
+          commissionCurrency: "CNY",
+          otherCurrency: "CNY",
+        };
+        proxy.post("/contractBudget/budget", data).then((res) => {
+          console.log("预算成功");
+        });
+      } else {
+        return false;
+      }
+    }
     return true;
   } else {
     setTimeout(() => {
@@ -1294,7 +1520,7 @@ defineExpose({
   getFormData,
   handleSubmit,
 });
-
+const contractShipmentList = ref([]);
 const getAssociationData = (type, id) => {
   if (type && id) {
     if (type == 1) {
@@ -1308,6 +1534,7 @@ const getAssociationData = (type, id) => {
             claimTime: res.createTime,
           },
         ];
+        contractShipmentList.value = res.contractShipmentList;
         getAuxiliaryData();
       });
     } else if (type == 2) {
@@ -1319,6 +1546,7 @@ const getAssociationData = (type, id) => {
             claimTime: res.createTime,
           },
         ];
+        contractShipmentList.value = res.sampleShipmentLists;
         getAuxiliaryData();
       });
     }
@@ -1396,15 +1624,38 @@ onMounted(() => {
             fileList: [],
           };
         });
-        formData.data.purchaseArrivalList = res.rows.map((item) => {
-          return {
-            productCode: item.productCode,
-            productId: item.productId,
-            productName: item.productName,
-            arrivalTime: "",
-            quantity: undefined,
-          };
-        });
+        if (
+          contractShipmentList.value &&
+          contractShipmentList.value.length > 0
+        ) {
+          formData.data.purchaseArrivalList = res.rows.map((item) => {
+            let current = contractShipmentList.value.find(
+              (x) => x.productId == item.productId
+            );
+            return {
+              productCode: item.productCode,
+              productId: item.productId,
+              productName: item.productName,
+              arrivalTime: current ? current.shipmentTime : "",
+              quantity: current ? current.quantity : "",
+            };
+          });
+        } else {
+          setTimeout(() => {
+            formData.data.purchaseArrivalList = res.rows.map((item) => {
+              let current = contractShipmentList.value.find(
+                (x) => x.productId == item.productId
+              );
+              return {
+                productCode: item.productCode,
+                productId: item.productId,
+                productName: item.productName,
+                arrivalTime: current ? current.shipmentTime : "",
+                quantity: current ? current.quantity : "",
+              };
+            });
+          }, 1500);
+        }
         let fileIds = formData.data.purchaseProductList.map(
           (item) => item.productId
         );
@@ -2044,6 +2295,30 @@ const openDetails = (id) => {
     });
   }
 };
+
+const getColor = (row) => {
+  if (
+    row.purchaseProductPriceListCopy &&
+    row.purchaseProductPriceListCopy.length > 0
+  ) {
+    let arr = row.purchaseProductPriceListCopy.map((x) => x.price);
+    let minPrice = Math.min(...arr);
+    let maxPrice = Math.max(...arr);
+    if (row.price && row.price != 0) {
+      if (row.price > maxPrice) {
+        return "#ff041c";
+      } else if (row.price < minPrice) {
+        return "#edd067";
+      } else {
+        return "#85c1a6";
+      }
+    } else {
+      return "#85c1a6";
+    }
+  } else {
+    return "#85c1a6";
+  }
+};
 </script>
 
 <style lang="scss" scoped>

+ 31 - 1
src/components/process/EHSD/PurchaseChange.vue

@@ -295,7 +295,7 @@
                                   </template>
                                   <template #reference>
                                     <div style="margin-left:10px;cursor:pointer;position:relative;top:4px">
-                                      <el-icon :size="20" color="#85c1a6">
+                                      <el-icon :size="20" :color="getColor(row)">
                                         <WarningFilled />
                                       </el-icon>
                                     </div>
@@ -952,6 +952,12 @@ const changeProductPrice = () => {
             const jele = iele.purchaseProductMountingsList[j];
             for (const key in resOne) {
               if (jele.productId == key) {
+                jele.purchaseProductPriceListCopy = resOne[
+                  key
+                ].purchaseProductList.map((x) => ({
+                  createTime: x.createTime,
+                  price: x.price,
+                }));
                 jele.purchaseProductPriceList = resOne[key].purchaseProductList
                   .map((x) => ({
                     createTime: x.createTime,
@@ -1995,6 +2001,30 @@ const openDetails = (id) => {
     });
   }
 };
+
+const getColor = (row) => {
+  if (
+    row.purchaseProductPriceListCopy &&
+    row.purchaseProductPriceListCopy.length > 0
+  ) {
+    let arr = row.purchaseProductPriceListCopy.map((x) => x.price);
+    let minPrice = Math.min(...arr);
+    let maxPrice = Math.max(...arr);
+    if (row.price && row.price != 0) {
+      if (row.price > maxPrice) {
+        return "#ff041c";
+      } else if (row.price < minPrice) {
+        return "#edd067";
+      } else {
+        return "#85c1a6";
+      }
+    } else {
+      return "#85c1a6";
+    }
+  } else {
+    return "#85c1a6";
+  }
+};
 </script>
 
 <style lang="scss" scoped>

+ 13 - 15
src/layout/index.vue

@@ -1,19 +1,8 @@
 <template>
-  <div
-    :class="classObj"
-    class="app-wrapper"
-    :style="{ '--current-color': theme }"
-  >
-    <div
-      v-if="device === 'mobile' && sidebar.opened"
-      class="drawer-bg"
-      @click="handleClickOutside"
-    />
+  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
+    <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
     <sidebar v-if="!sidebar.hide" class="sidebar-container" />
-    <div
-      :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
-      class="main-container"
-    >
+    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
       <div :class="{ 'fixed-header': fixedHeader }">
         <navbar @setLayout="setLayout" />
         <headerBar></headerBar>
@@ -21,7 +10,7 @@
       </div>
       <app-main />
       <settings ref="settingRef" />
-      
+
     </div>
   </div>
 </template>
@@ -74,6 +63,15 @@ const settingRef = ref(null);
 function setLayout() {
   settingRef.value.openSetting();
 }
+const { proxy } = getCurrentInstance();
+const getAllDict = () => {
+  proxy
+    .useUserStore()
+    .allDictMap()
+    .then(() => {})
+    .catch(() => {});
+};
+getAllDict();
 </script>
 
 <style lang="scss" scoped>

+ 14 - 5
src/main.js

@@ -22,8 +22,6 @@ const uploadUrl =
   prefix +
   import.meta.env.VITE_APP_UPLOAD_API +
   import.meta.env.VITE_APP_UPLOAD_BASE_API + '/open/fileInfo/upload'
-console.log(uploadUrl, 'ssssssssss');
-
 // 注册指令
 import plugins from './plugins' // plugins
 import {
@@ -54,10 +52,10 @@ import {
 } from '@/utils/ruoyi'
 
 
-
 import {
   dictDataEcho,
   dictValueLabel,
+  dictKeyValue,
   moneyFormat,
   calculationWeek,
   getDict,
@@ -69,9 +67,13 @@ import {
   deepClone,
   timeInterval,
   compareTime,
-  getImgBase64
+  getImgBase64,
+  getFileData,
+  msgTip,
+  msgConfirm
 } from '@/utils/util'
-
+// userStore
+import useUserStore from "@/store/modules/user";
 // 分页组件
 import Pagination from '@/components/Pagination'
 // 自定义表格工具组件
@@ -103,6 +105,8 @@ app.config.globalProperties.get = get
 app.config.globalProperties.post = post
 app.config.globalProperties.postTwo = postTwo
 app.config.globalProperties.download = download
+app.config.globalProperties.msgTip = msgTip
+app.config.globalProperties.msgConfirm = msgConfirm
 app.config.globalProperties.parseTime = parseTime
 app.config.globalProperties.resetForm = resetForm
 app.config.globalProperties.handleTree = handleTree
@@ -110,7 +114,9 @@ app.config.globalProperties.addDateRange = addDateRange
 app.config.globalProperties.selectDictLabel = selectDictLabel
 app.config.globalProperties.selectDictLabels = selectDictLabels
 app.config.globalProperties.t = i18n.global.t
+app.config.globalProperties.getFileData = getFileData
 //字典回显
+app.config.globalProperties.dictKeyValue = dictKeyValue
 app.config.globalProperties.dictDataEcho = dictDataEcho
 app.config.globalProperties.dictValueLabel = dictValueLabel
 app.config.globalProperties.moneyFormat = moneyFormat
@@ -125,6 +131,9 @@ app.config.globalProperties.deepClone = deepClone
 app.config.globalProperties.timeInterval = timeInterval
 app.config.globalProperties.compareTime = compareTime
 app.config.globalProperties.getImgBase64 = getImgBase64
+// user
+app.config.globalProperties.useUserStore = useUserStore
+
 
 
 

+ 17 - 2
src/store/modules/user.js

@@ -1,7 +1,8 @@
 import {
   login,
   logout,
-  getInfo
+  getInfo,
+  allDictMap
 } from '@/api/login'
 import {
   getToken,
@@ -19,6 +20,7 @@ const useUserStore = defineStore(
       roles: [],
       permissions: [],
       user: {},
+      allDict: {},
     }),
     actions: {
       // 登录
@@ -73,7 +75,20 @@ const useUserStore = defineStore(
             reject(error)
           })
         })
-      }
+      },
+      // 获取字典
+      allDictMap() {
+        return new Promise((resolve, reject) => {
+          allDictMap()
+            .then((res) => {
+              this.allDict = res.data;
+              resolve("");
+            })
+            .catch((error) => {
+              reject(error);
+            });
+        });
+      },
     }
   })
 

+ 100 - 1
src/utils/util.js

@@ -7,6 +7,71 @@ import Cookies from "js-cookie";
 import html2Canvas from "html2canvas";
 import JsPDF from "jspdf";
 import * as toEnglish from "./ACapital.js";
+import {
+  ElMessage,
+  ElMessageBox
+} from 'element-plus'
+
+// businessIdList 业务id列表  data 数据源  att  要对应的属性   businessType取业务类型的数据  fileAtt  要赋值的属性  filePathAtt  取第一张图片的属性
+export function getFileData(option = {}) {
+  return new Promise((resolve) => {
+    let {
+      businessIdList,
+      data,
+      att = 'id',
+      businessType = '',
+      fileAtt = "fileList",
+      filePathAtt = 'imageUrl',
+      getAll = false
+    } = option
+    if (businessIdList && businessIdList.length > 0) {
+      if (getAll) {
+        resolve(post("/fileInfo/getList", {
+          businessIdList,
+        }))
+        return
+      } else {
+        post("/fileInfo/getList", {
+          businessIdList,
+        }).then((res) => {
+          if (data && data.length > 0) {
+            for (let i = 0; i < data.length; i++) {
+              const ele = data[i];
+              for (const key in res) {
+                if (ele[att] == key && res[key] && res[key].length > 0) {
+                  let list = []
+                  if (businessType) {
+                    list = res[key].filter((x) => x.businessType == businessType)
+                  } else {
+                    list = res[key]
+                  }
+                  ele[fileAtt] = list
+                  if (filePathAtt) {
+                    ele[filePathAtt] = list[0].fileUrl
+                  }
+                }
+              }
+            }
+          }
+        });
+      }
+    }
+  })
+
+}
+
+//根据dictKey值回显字典label值
+export function dictKeyValue(value, arr) {
+  if ((value || value == 0) && arr) {
+    value = value + "";
+    const current = arr.find((x) => x.dictKey == value);
+    if (current != undefined && current.dictValue) {
+      return current.dictValue;
+    }
+    return "";
+  }
+  return "";
+}
 
 //根据value值回显字典label值
 export function dictDataEcho(value, arr) {
@@ -450,4 +515,38 @@ export function getImgBase64(url) {
       reject("图片加载失败");
     };
   });
-};
+};
+
+// 消息提示
+export function msgTip(message, type = 1) {
+  let obj = {
+    1: 'success',
+    2: "info",
+    3: "warning",
+    4: "error"
+  }
+  ElMessage({
+    type: obj[type],
+    message,
+  })
+
+}
+
+// 消息确认
+export function msgConfirm(title = '您是否确认执行此操作? ') {
+  return new Promise((resolve, reject) => {
+    ElMessageBox.confirm(
+      title,
+      "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning",
+      }
+    ).then((res) => {
+      resolve(res)
+    }).catch((err) => {
+      reject(err)
+    })
+  })
+
+}

+ 22 - 3
src/views/EHSD/procurement/profitBudgetEHSD/index.vue

@@ -58,14 +58,22 @@
                       <el-collapse-item title="历史记录表" name="1">
                         <el-table :data="row.grossProfitInfoListOne" style="width: 100%;">
                           <el-table-column prop="createTime" label="时间" width="160" />
-                          <el-table-column label="毛利" width="110">
+                          <el-table-column label="毛利" width="90">
                             <template #default="scope">
                               <div>
                                 <span>{{ moneyFormat(scope.row.gross,2) }}</span>
                               </div>
                             </template>
                           </el-table-column>
-                          <el-table-column label="销售合同金额">
+                          <el-table-column prop="grossRate" label="毛利率(%)" width="90" />
+                          <el-table-column label="对比上一版本利润" width="150">
+                            <template #default="scope">
+                              <div>
+                                <span>{{ moneyFormat(scope.row.compareLastGross,2) }}</span>
+                              </div>
+                            </template>
+                          </el-table-column>
+                          <el-table-column label="销售合同金额" width="150">
                             <template #default="scope">
                               <div>
                                 <span v-if="scope.$index>0 && getIsTop(row.grossProfitInfoListOne,scope,'contractAmount')"
@@ -81,7 +89,7 @@
                               </div>
                             </template>
                           </el-table-column>
-                          <el-table-column label="采购合同金额">
+                          <el-table-column label="采购合同金额" width="150">
                             <template #default="scope">
                               <div>
                                 <span v-if="scope.$index>0 && getIsTop(row.grossProfitInfoListOne,scope,'purchaseAmount')"
@@ -994,6 +1002,17 @@ const optionTwo = reactive({
         ${params[0].axisValue}
         <br/> 
         ${params[0].seriesName}:${params[0].data} 
+        <br/> 毛利率:${
+          showRowData.value.grossProfitInfoList[params[0].dataIndex + 1]
+            .grossRate
+        }%
+        <br/> 对比上一版本利润:${
+          showRowData.value.grossProfitInfoList[params[0].dataIndex + 1]
+            .contractCurrency
+        } ${
+          showRowData.value.grossProfitInfoList[params[0].dataIndex + 1]
+            .compareLastGross
+        }
         <br/> 销售合同金额:${
           showRowData.value.grossProfitInfoList[params[0].dataIndex + 1]
             .contractCurrency

+ 23 - 0
src/views/EHSD/saleContract/contractEHSD/index.vue

@@ -481,6 +481,20 @@ const config = computed(() => {
                 },
               }
             : {},
+          row.notPack == 1
+            ? {
+                attrs: {
+                  label: "装箱出货",
+                  type: "primary",
+                  text: true,
+                },
+                el: "button",
+                click() {
+                  pushRoute(row);
+                },
+              }
+            : {},
+
           {
             attrs: {
               label: "打印",
@@ -1140,6 +1154,15 @@ const moreSearchReset = () => {
   };
   moreSearchQuery();
 };
+
+const pushRoute = (row) => {
+  proxy.$router.push({
+    name: "Packing",
+    query: {
+      contractId: row.id,
+    },
+  });
+};
 </script>
 
 <style lang="scss" scoped>

+ 3 - 1
src/views/finance/fundManage/accountPayment/index.vue

@@ -230,7 +230,9 @@ const config = computed(() => {
         width: 130,
       },
       render(type) {
-        if (type != "20") {
+        if (type == "40") {
+          return "售后";
+        } else if (type != "20") {
           return "请款: " + proxy.dictValueLabel(type, fundsType.value);
         } else {
           return "采购付款 - 申请";

+ 52 - 3
src/views/process/processApproval/index.vue

@@ -25,6 +25,10 @@
           <ContractEHSD ref="makeDom" :queryData="queryData.data" @auxiliaryChange="(e) => getAuxiliaryData(e)"></ContractEHSD>
         </template>
 
+        <!-- 售后 -->
+        <AfterSales ref="makeDom" :queryData="queryData.data" v-else-if="flowForm.flowKey == 'after_sales_flow'">
+        </AfterSales>
+
         <!-- 销售合同变更 -->
         <template v-else-if="flowForm.flowKey == 'contract_update_flow'">
           <ContractChangeEHSD ref="makeDom" :queryData="queryData.data" @auxiliaryChange="(e) => getAuxiliaryData(e)"></ContractChangeEHSD>
@@ -136,8 +140,11 @@
                 </div>
               </el-form-item>
               <el-form-item>
-                <el-button type="primary" v-if="approvalRecordData.buttonInfoList.length == 0" @click="handleSubmit"
-                           :loading="btnLoading">提交</el-button>
+                <div v-if="approvalRecordData.buttonInfoList.length == 0">
+                  <el-button type="primary" @click="handleSubmit" :loading="btnLoading">提交</el-button>
+                  <el-button type="primary" v-if="StagFlowKey.includes(flowForm.flowKey)" @click="handleSubmitStag"
+                             :loading="btnLoading">暂存</el-button>
+                </div>
                 <el-button type="primary" v-else v-for="i in approvalRecordData.buttonInfoList" :key="i.type" :loading="btnLoading"
                            @click="handleSubmit(i.type)" style="margin-bottom:10px">{{ i.name }}</el-button>
               </el-form-item>
@@ -230,6 +237,8 @@ import PurchaseEHSD from "@/components/process/EHSD/Purchase";
 import PurchaseChangeEHSD from "@/components/process/EHSD/PurchaseChange";
 // 取消认领-EHSD
 import CancelClaim from "@/components/process/EHSD/CancelClaim";
+// 售后管理-EHSD
+import AfterSales from "@/components/process/EHSD/AfterSales";
 
 //申购发起
 import SendSubscribe from "@/components/process/SendSubscribe";
@@ -322,6 +331,41 @@ const handleSuccess = (any, UploadFile) => {
 const onPreviewFile = (file) => {
   window.open(file.raw.fileUrl, "_blank");
 };
+const StagFlowKey = ref(["after_sales_flow"]);
+// 暂存
+const handleSubmitStag = async (_type) => {
+  try {
+    // 调用发起组件的提交事件
+    const flag = await makeDom.value.handleSubmit(true);
+    if (flag) {
+      flowFormDom.value.validate((valid) => {
+        btnLoading.value = true;
+        if (valid) {
+          const data = { ...makeDom.value.getFormData() };
+          flowForm.fileList = flowForm.fileList.map((item) => {
+            return {
+              ...item,
+              ...item.raw,
+            };
+          });
+          if (flowForm.flowKey == "after_sales_flow") {
+            proxy.post("/afterSales/add", data).then(
+              (res) => {
+                skipPage();
+              },
+              (err) => {
+                btnLoading.value = false;
+              }
+            );
+          }
+        }
+      });
+    }
+  } catch (err) {
+    console.log("数据未填完整!", err);
+  }
+};
+
 // 提交逻辑
 const handleSubmit = async (_type) => {
   handleType.value = _type ? _type : undefined;
@@ -618,6 +662,10 @@ const skipPage = () => {
       router.replace({
         name: "Claim",
       });
+    } else if (flowForm.flowKey == "after_sales_flow") {
+      router.replace({
+        name: "AfterSales",
+      });
     }
   }
 };
@@ -733,7 +781,8 @@ onMounted(async () => {
       userInfo.roles.includes("salesDirector") ||
       userInfo.roles.includes("financeOfficer") ||
       userInfo.roles.includes("approve_ accountant")) &&
-    (route.query.processType == 10 || route.query.processType == 30)
+    (route.query.processType == 10 || route.query.processType == 30) &&
+    route.query.flowKey != "after_sales_flow"
   ) {
     showChart.value = true;
   }

+ 765 - 0
src/views/salesMange/afterSales/SelectContractProduct.vue

@@ -0,0 +1,765 @@
+<template>
+  <div class="user">
+    <!-- <div class="tree">
+      <treeList title="产品分类" submitType="1" :hiddenBtn="true" :data="treeListData" v-model="sourceList.pagination.productClassifyId"
+                @change="treeChange" @changeTreeList="getTreeList">
+      </treeList>
+    </div> -->
+    <div class="content">
+      <byTable :source="sourceList.data" :tableHeight="tableHeight" :pagination="sourceList.pagination" :config="config" :loading="loading"
+               highlight-current-row :selectConfig="selectConfig" :table-events="{
+          //element talbe事件都能传
+          select: select,
+        }" :action-list="[]" @get-list="getList">
+
+        <template #pic="{ item }">
+          <div style="width:100%">
+            <img v-if="item.fileUrl" :src="item.fileUrl" class="pic" @click="openImg(item.fileUrl)" />
+          </div>
+        </template>
+
+        <template #size="{ item }">
+          <div v-if="item.productLength && item.productWidth && item.productHeight">
+            <span>{{ item.productLength}}</span>*
+            <span>{{ item.productWidth }}</span>*
+            <span>{{ item.productHeight }}</span>
+          </div>
+          <div v-else></div>
+        </template>
+        <template #price="{ item }">
+          <div v-if="item.price">
+            <span>{{ item.currency }} {{ moneyFormat(item.price ,2)}}</span>
+          </div>
+          <div v-else></div>
+        </template>
+      </byTable>
+    </div>
+    <!-- <div class="right">
+      <div style="margin-bottom:30px">
+        <TitleInfo :content="'已选择商品'"></TitleInfo>
+      </div>
+      <el-tag style="margin-right: 10px; margin-bottom: 10px" type="info" v-for="(good, index) in goodList" :key="good.productId">
+        {{ good.productCnName || good.productName }}
+      </el-tag>
+    </div> -->
+
+    <el-dialog :title="modalType == 'add' ? '添加产品' : '编辑产品'" v-model="dialogVisible" width="700" v-loading="submitLoading" destroy-on-close>
+      <div class="public_height_dialog">
+        <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="byform">
+          <template #nameEnglish>
+            <div style="width: 100%">
+              <el-form-item label="英文名" prop="nameEnglish">
+                <el-input v-model="formData.data.nameEnglish" placeholder="请输入" onkeyup="value=value.replace(/[^\x00-\xff]/g, '')"></el-input>
+                <!-- @input="(val) => handleKeyup(val)" -->
+              </el-form-item>
+            </div>
+          </template>
+          <template #productPic>
+            <div>
+              <el-upload v-model:fileList="fileList" :action="uploadUrl" :data="uploadData" list-type="picture-card" :on-remove="handleRemove"
+                         :before-upload="handleBeforeUpload" :on-preview="handlePreview" accept=".gif, .jpeg, .jpg, .png">
+                <el-icon>
+                  <Plus />
+                </el-icon>
+              </el-upload>
+            </div>
+          </template>
+        </byForm>
+      </div>
+      <template #footer>
+        <el-button @click="dialogVisible = false" size="default">取 消</el-button>
+        <el-button type="primary" @click="submitForm('byform')" size="default" :loading="submitLoading">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog title="导入产品" v-model="openExcelDialog" width="400" v-loading="excelLoading">
+      <el-upload :action="actionUrl + '/productInfo/excelImportByEhsd'" :headers="headers" :on-success="handleSuccess" :on-progress="handleProgress"
+                 :show-file-list="false" :on-error="handleError" accept=".xlsx">
+        <el-button type="primary">点击导入</el-button>
+      </el-upload>
+      <template #footer>
+        <el-button @click="openExcelDialog = false" size="default">取 消</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-if="productContractDialog" v-model="productContractDialog" :title="'外销合同'" width="80%" append-to-body>
+      <ProductContract :currentProductId="currentProductId"></ProductContract>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ElMessage, ElMessageBox } from "element-plus";
+import byTable from "@/components/byTable/index";
+import byForm from "@/components/byForm/index";
+import treeList from "@/components/product/treeList";
+import { getToken } from "@/utils/auth";
+import ProductContract from "@/components/contractCom/productContract.vue";
+import TitleInfo from "@/components/TitleInfo/index.vue";
+import { watch } from "vue";
+const { proxy } = getCurrentInstance();
+const props = defineProps({
+  alreadySelectData: Array,
+  companyId: {
+    type: String,
+    default: "",
+  },
+  // 过滤是否配置过bom
+  isRawMaterial: {
+    type: String,
+    default: "",
+  },
+});
+const tableHeight = ref(0);
+const getTableHeight = () => {
+  tableHeight.value = window.innerHeight - 245;
+};
+getTableHeight();
+window.addEventListener("resize", () => {
+  getTableHeight();
+});
+const goodList = ref([]);
+onMounted(() => {
+  // goodList.value = proxy.deepClone(props.alreadySelectData);
+});
+const headers = ref({ Authorization: "Bearer " + getToken() });
+const actionUrl = import.meta.env.VITE_APP_BASE_API;
+const loading = ref(false);
+const submitLoading = ref(false);
+const treeListData = ref([]);
+const innerMethon = ref([]);
+const outsideMethon = ref([]);
+const productUnit = ref([]);
+const companyData = ref([]);
+const accountCurrency = ref([]);
+const sourceList = ref({
+  data: [],
+  pagination: {
+    total: 3,
+    pageNum: 1,
+    pageSize: 10,
+    keyword: "",
+  },
+});
+let dialogVisible = ref(false);
+let openExcelDialog = ref(false);
+let excelLoading = ref(false);
+let modalType = ref("add");
+let rules = ref({
+  productClassifyId: [
+    { required: true, message: "请选择产品分类", trigger: "change" },
+  ],
+  name: [{ required: true, message: "请输入产品名称", trigger: "blur" }],
+  nameEnglish: [
+    { required: true, message: "请输入产品英文名", trigger: "blur" },
+  ],
+  productLong: [
+    { required: true, message: "请输入长 (cm)", trigger: "blur" },
+  ],
+  productWide: [
+    { required: true, message: "请输入宽 (cm)", trigger: "blur" },
+  ],
+  productHigh: [
+    { required: true, message: "请输入高 (cm)", trigger: "blur" },
+  ],
+  innerPackMethod: [
+    { required: true, message: "请选择内包装方式", trigger: "change" },
+  ],
+  outerPackMethod: [
+    { required: true, message: "请选择外包装方式", trigger: "change" },
+  ],
+});
+
+const selectConfig = computed(() => [
+  // {
+  //   label: "业务公司",
+  //   prop: "companyId",
+  //   data: companyData.value,
+  // },
+]);
+const config = computed(() => {
+  return [
+    // {
+    //   attrs: {
+    //     label: "工厂",
+    //     prop: "corporationName",
+    //     width: 110,
+    //   },
+    // },
+    {
+      attrs: {
+        label: "订单号",
+        prop: "contractCode",
+        width: 150,
+      },
+    },
+    {
+      attrs: {
+        label: "图片",
+        slot: "pic",
+        align: "center",
+        width: 80,
+      },
+    },
+    {
+      attrs: {
+        label: "产品分类",
+        prop: "productCategory",
+        width: 200,
+      },
+    },
+    {
+      attrs: {
+        label: "产品编码",
+        prop: "productCode",
+        width: 180,
+      },
+    },
+    {
+      attrs: {
+        label: "产品名称",
+        prop: "productName",
+        "min-width": 200,
+      },
+    },
+    {
+      attrs: {
+        label: "规格尺寸 (cm)",
+        prop: "productModel",
+        width: 130,
+      },
+    },
+    // {
+    //   attrs: {
+    //     label: "颜色",
+    //     prop: "productColor",
+    //     width: 120,
+    //   },
+    // },
+    {
+      attrs: {
+        label: "订单数量",
+        prop: "quantity",
+        width: 120,
+      },
+    },
+    {
+      attrs: {
+        label: "操作",
+        width: "80",
+        align: "center",
+        fixed: "right",
+      },
+      // 渲染 el-button,一般用在最后一列。
+      renderHTML(row) {
+        return [
+          {
+            attrs: {
+              label: "选择",
+              type: "primary",
+              text: true,
+            },
+            el: "button",
+            click() {
+              clickSelect(row);
+            },
+          },
+        ];
+      },
+    },
+  ];
+});
+
+const uploadData = ref({});
+const fileList = ref([]);
+const fileListCopy = ref([]);
+let formData = reactive({
+  data: {},
+});
+const formOption = reactive({
+  disabled: false,
+  inline: true,
+  labelWidth: 100,
+  itemWidth: 100,
+  rules: [],
+});
+const byform = ref(null);
+const formConfig = computed(() => {
+  return [
+    {
+      type: "title",
+      title: "基本信息",
+    },
+    {
+      type: "treeSelect",
+      prop: "productClassifyId",
+      label: "产品分类",
+      data: treeListData.value,
+      itemWidth: 100,
+      disabled: false,
+      style: {
+        width: "100%",
+      },
+    },
+    {
+      type: "input",
+      prop: "name",
+      label: "产品名称",
+      itemWidth: 100,
+      disabled: false,
+    },
+    {
+      type: "slot",
+      slotName: "nameEnglish",
+      label: "",
+    },
+    {
+      type: "slot",
+      slotName: "productPic",
+      prop: "fileList",
+      label: "产品图片",
+    },
+    {
+      type: "title",
+      title: "价格信息",
+    },
+    {
+      type: "selectInput",
+      prop: "price",
+      selectProp: "currency",
+      label: "销售业务成本单价",
+      itemWidth: 50,
+      style: {
+        width: "100%",
+      },
+      data: accountCurrency.value,
+    },
+    {
+      type: "selectInput",
+      prop: "costPrice",
+      selectProp: "costCurrency",
+      label: "成本价",
+      itemWidth: 50,
+      style: {
+        width: "100%",
+      },
+      data: accountCurrency.value,
+    },
+    {
+      type: "title",
+      title: "属性信息",
+    },
+    {
+      type: "input",
+      prop: "spec",
+      label: "规格型号",
+      itemWidth: 100,
+      disabled: false,
+    },
+    {
+      type: "input",
+      prop: "productLong",
+      label: "尺寸",
+      itemWidth: 33.33,
+      placeholder: "长(cm)",
+      disabled: false,
+    },
+    {
+      type: "input",
+      prop: "productWide",
+      label: " ",
+      itemWidth: 33.33,
+      placeholder: "宽(cm)",
+      disabled: false,
+    },
+    {
+      type: "input",
+      prop: "productHigh",
+      label: " ",
+      itemWidth: 33.33,
+      placeholder: "高(cm)",
+      disabled: false,
+    },
+    {
+      type: "select",
+      prop: "innerPackMethod",
+      label: "内包装方式",
+      required: true,
+      itemWidth: 50,
+      multiple: true,
+      data: innerMethon.value,
+      filterable: true,
+      placeholder: "内包装方式",
+      style: {
+        width: "100%",
+      },
+      disabled: false,
+    },
+    {
+      type: "select",
+      prop: "outerPackMethod",
+      label: "外包装方式",
+      required: true,
+      itemWidth: 50,
+      multiple: true,
+      data: outsideMethon.value,
+      filterable: true,
+      placeholder: "外包装方式",
+      style: {
+        width: "100%",
+      },
+      disabled: false,
+    },
+    {
+      type: "input",
+      prop: "netWeight",
+      label: "净重(kg)",
+      itemWidth: 100,
+      style: {
+        width: "30%",
+      },
+    },
+    {
+      type: "input",
+      prop: "hsCode",
+      label: "海关编码",
+      itemWidth: 100,
+      style: {
+        width: "30%",
+      },
+      disabled: false,
+    },
+    {
+      type: "input",
+      itemType: "textarea",
+      prop: "remark",
+      label: "备注",
+      itemWidth: 100,
+    },
+  ];
+});
+
+const getList = async (req) => {
+  sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
+  loading.value = true;
+  proxy
+    .post("/contractProduct/afterSalesPage", sourceList.value.pagination)
+    .then(
+      (message) => {
+        sourceList.value.data = message.rows;
+        sourceList.value.pagination.total = message.total;
+        setTimeout(() => {
+          loading.value = false;
+        }, 200);
+        const productIdList = message.rows.map((x) => x.productId);
+        // 请求文件数据并回显
+        if (productIdList && productIdList.length > 0) {
+          proxy.getFileData({
+            businessIdList: productIdList,
+            data: sourceList.value.data,
+            att: "productId",
+            businessType: "0",
+            fileAtt: "fileList",
+            filePathAtt: "fileUrl",
+          });
+        }
+      },
+      (err) => {
+        loading.value = false;
+      }
+    );
+};
+
+const treeChange = (e) => {
+  sourceList.value.pagination.productClassifyId = e.id;
+  getList({ productClassifyId: e.id });
+};
+
+const openModal = () => {
+  dialogVisible.value = true;
+  modalType.value = "add";
+  formData.data = {
+    definition: "1",
+    outerPackMethod: [],
+    innerPackMethod: [],
+    fileList: [],
+    fileListCopy: [],
+    currency: "",
+    costCurrency: "",
+  };
+  if (accountCurrency.value && accountCurrency.value.length > 0) {
+    formData.data.currency = accountCurrency.value[0].value;
+    formData.data.costCurrency = accountCurrency.value[0].value;
+  }
+  fileList.value = [];
+  fileListCopy.value = [];
+};
+
+const openExcel = () => {
+  openExcelDialog.value = true;
+};
+
+const needAtt = [
+  "productClassifyId",
+  "name",
+  "spec",
+  "remark",
+  "fileList",
+  "id",
+  "unit",
+  "definition",
+];
+const submitForm = () => {
+  byform.value.handleSubmit((valid) => {
+    // if (!fileListCopy.value.length > 0) {
+    //   return ElMessage({
+    //     message: "请上传产品图片",
+    //     type: "info",
+    //   });
+    // }
+    let jsonObj = {};
+    formData.data.fileList = fileListCopy.value.map((x) => ({
+      id: x.id,
+      fileName: x.fileName,
+    }));
+    for (const key in formData.data) {
+      if (needAtt.includes(key)) {
+      } else {
+        jsonObj[key] = formData.data[key];
+        delete formData.data[key];
+      }
+    }
+    jsonObj.innerPackMethod = jsonObj.innerPackMethod.join(",");
+    jsonObj.outerPackMethod = jsonObj.outerPackMethod.join(",");
+    jsonObj.type = "1"; //1为公司产品库
+    formData.data.ehsdJson = JSON.stringify(jsonObj);
+    submitLoading.value = true;
+    proxy.post(`/productInfo/${modalType.value}ByEhsd`, formData.data).then(
+      (res) => {
+        ElMessage({
+          message: modalType.value == "add" ? "添加成功" : "编辑成功",
+          type: "success",
+        });
+        dialogVisible.value = false;
+        submitLoading.value = false;
+        getList();
+      },
+      (err) => {
+        for (const key in jsonObj) {
+          formData.data[key] = jsonObj[key];
+        }
+        formData.data.innerPackMethod =
+          formData.data.innerPackMethod.split(",");
+        formData.data.outerPackMethod =
+          formData.data.outerPackMethod.split(",");
+        submitLoading.value = false;
+      }
+    );
+  });
+};
+const getTreeList = () => {
+  proxy
+    .post("/productClassify/tree", { parentId: "", name: "", definition: "1" })
+    .then((message) => {
+      treeListData.value = message;
+    });
+};
+const getDtl = (row) => {
+  modalType.value = "edit";
+  proxy.post("/productInfo/detailByEhsd", { id: row.id }).then((res) => {
+    res.definition = "1"; //产品
+    let jsonObj = JSON.parse(res.ehsdJson);
+    res = {
+      ...res,
+      currency: jsonObj.currency
+        ? jsonObj.currency
+        : accountCurrency.value[0].value,
+      costCurrency: jsonObj.costCurrency
+        ? jsonObj.costCurrency
+        : accountCurrency.value[0].value,
+      ...jsonObj,
+    };
+    if (res.innerPackMethod) {
+      res.innerPackMethod = res.innerPackMethod.split(",");
+    } else {
+      res.innerPackMethod = [];
+    }
+    if (res.outerPackMethod) {
+      res.outerPackMethod = res.outerPackMethod.split(",");
+    } else {
+      res.outerPackMethod = [];
+    }
+    formData.data = res;
+    dialogVisible.value = true;
+    proxy
+      .post("/fileInfo/getList", { businessIdList: [row.id] })
+      .then((fileObj) => {
+        if (fileObj[row.id]) {
+          fileList.value = fileObj[row.id].map((x) => ({
+            ...x,
+            url: x.fileUrl,
+          }));
+          fileListCopy.value = fileObj[row.id].map((x) => ({
+            ...x,
+            url: x.fileUrl,
+          }));
+        } else {
+          fileList.value = [];
+          fileListCopy.value = [];
+        }
+      });
+  });
+};
+const isdisabled = ["price", "costPrice", "remark", "netWeight"];
+// watch(modalType, (val) => {
+//   if (val) {
+//     for (let i = 0; i < formConfig.value.length; i++) {
+//       const element = formConfig.value[i];
+//       if (element.type != "title" || element.type != "slot") {
+//         if (!isdisabled.includes(element.prop)) {
+//           element.disabled = val == "edit" ? true : false;
+//         }
+//       }
+//     }
+//   }
+// });
+const handleBeforeUpload = async (file) => {
+  const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
+  uploadData.value = res.uploadBody;
+  fileListCopy.value.push({
+    id: res.id,
+    fileName: res.fileName,
+    path: res.fileUrl,
+    url: res.fileUrl,
+    uid: file.uid,
+  });
+};
+const handleRemove = (file) => {
+  const index = fileListCopy.value.findIndex(
+    (x) => x.uid === file.uid || x.id === file.id
+  );
+  fileListCopy.value.splice(index, 1);
+};
+const handleClickFile = (file) => {
+  window.open(file.fileUrl, "_blank");
+};
+const handleProgress = () => {
+  excelLoading.value = true;
+};
+const handleError = (err) => {
+  ElMessage({
+    message: `${err},请重试!`,
+    type: "info",
+  });
+  openExcelDialog.value = false;
+  excelLoading.value = false;
+};
+const handleSuccess = (res) => {
+  if (res.code != 200) {
+    return ElMessage({
+      message: `${res.msg},请重试!`,
+      type: "info",
+    });
+  } else {
+    ElMessage({
+      message: "导入成功!",
+      type: "success",
+    });
+    openExcelDialog.value = false;
+    excelLoading.value = false;
+    getList();
+  }
+};
+const getDict = () => {
+  proxy
+    .get("/tenantDept/list", {
+      pageNum: 1,
+      pageSize: 9999,
+      keyword: "",
+      tenantId: proxy.useUserStore().user.tenantId,
+      type: 0,
+    })
+    .then((res) => {
+      companyData.value = res.data.map((x) => ({
+        ...x,
+        label: x.deptName,
+        value: x.deptId,
+      }));
+      // treeDataOne.value = proxy.handleTree(res.data, "deptId");
+    });
+};
+// getDict();
+// getTreeList();
+
+if (props && props.companyId) {
+  sourceList.value.pagination.companyId = props.companyId;
+}
+if (props && props.isRawMaterial) {
+  sourceList.value.pagination.isRawMaterial = props.isRawMaterial;
+}
+
+getList();
+const clickSelect = (item) => {
+  // item.selectType = "1";
+  // goodList.value.push({
+  //   ...item,
+  //   productName: item.name,
+  // });
+  proxy.$emit("selectProduct", item);
+};
+
+const handleKeypress = (event) => {
+  // 判断输入字符是否为中文字符
+  if (event.key.match(/[\u4e00-\u9fa5]/)) {
+    // 阻止输入
+    event.preventDefault();
+  }
+};
+const handleKeyup = (val) => {
+  // 过滤掉中文字符
+  formData.data.nameEnglish = formData.data.nameEnglish.replace(
+    /[\u4e00-\u9fa5]/g,
+    ""
+  );
+};
+
+const handlePreview = (file) => {
+  if (file && file.fileUrl) {
+    window.open(file.fileUrl, "_black");
+  }
+};
+const productContractDialog = ref(false);
+const currentProductId = ref("");
+const handleOpenProductContract = (row) => {
+  currentProductId.value = row.id;
+  productContractDialog.value = true;
+};
+</script>
+
+<style lang="scss" scoped>
+.user {
+  // padding: 10px;
+  display: flex;
+  justify-content: space-between;
+  // .tree {
+  //   width: 300px;
+  //   border-right: 1px solid rgb(223, 221, 221);
+  // }
+  .content {
+    width: 100%;
+    // width: calc(100% - 310px);
+  }
+  .right {
+    padding-left: 10px;
+    width: 170px;
+    border-left: 1px solid #eee;
+  }
+}
+.pic {
+  object-fit: contain;
+  width: 50px;
+  height: 50px;
+  cursor: pointer;
+  vertical-align: middle;
+}
+</style>

+ 881 - 0
src/views/salesMange/afterSales/index.vue

@@ -0,0 +1,881 @@
+<template>
+  <div class="pageIndexClass">
+
+    <div>
+      <byTable :source="sourceList.data" :pagination="sourceList.pagination" :config="config" :loading="loading" highlight-current-row
+               :selectConfig="selectConfig" :table-events="{
+          //element talbe事件都能传
+        }" :action-list="[
+          {
+            text: '添加售后',
+            disabled: false,
+            action: () => clickAdd(),
+          },
+        ]" @get-list="getList">
+
+        <template #amount="{ item }">
+          <div style="width:100%">
+            <span class="el-click" style="cursor:pointer;color:#409EFF" @click="getDtl(item)">{{moneyFormat(item.amount,2)}}</span>
+          </div>
+        </template>
+
+        <template #pic="{ item }">
+          <div style="width:100%">
+            <img v-if="item.fileUrl" :src="item.fileUrl" class="pic" @click="openImg(item.fileUrl)" />
+          </div>
+        </template>
+
+        <template #size="{ item }">
+          <div v-if="item.productLength && item.productWidth && item.productWidth">
+            <span>{{ item.productLength }}</span>*
+            <span>{{ item.productWidth }}</span>*
+            <span>{{ item.productWidth }}</span>
+          </div>
+          <div v-else></div>
+        </template>
+
+        <template #follow="{ item }">
+          <div :class="'getWidth' + item.id" style="width: 100%">
+            <div style="width: 100%; display: flex">
+              <template v-if="
+                item.afterSalesRecordsList &&
+                item.afterSalesRecordsList.length > 0
+              ">
+                <div :style="
+                  index > 2
+                    ? 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer; display: none'
+                    : 'line-height: 32px; margin-right: 8px; padding: 0 8px; background-color: #eeeeee; border-radius: 4px; cursor: pointer'
+                " v-for="(record, index) in item.afterSalesRecordsList" :key="record.id">
+                  <el-popover placement="bottom" :width="300" trigger="hover" @show="recordShow(record)">
+                    <template #reference>
+                      <div>
+                        <span v-if="record.followUpTime">{{
+                        record.followUpTime.substr(0, 10)
+                      }}</span>
+                        <el-icon style="margin-left: 8px; transform: translateY(2px)" @click="deleteFollow(record)">
+                          <DeleteFilled />
+                        </el-icon>
+                      </div>
+                    </template>
+                    <template #default>
+                      <div style="width: 100%">
+                        <div style="color: #909399; margin: 8px 0">
+                          跟进时间: {{ record.followUpTime }}
+                        </div>
+                        <div style="margin-top: 8px">
+                          跟进人:
+                          {{ dictValueLabel(record.createUser, userList) }}
+                        </div>
+                        <div style="margin-top: 8px">
+                          跟进内容: {{ record.remark }}
+                        </div>
+                        <div style="margin: 8px 0; display: flex" v-if="record.fileList && record.fileList.length > 0">
+                          <div style="width: 36px">附件:</div>
+                          <div style="width: calc(100% - 36px)">
+                            <div v-for="(file, index) in record.fileList" :key="index">
+                              <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </template>
+                  </el-popover>
+                </div>
+                <div style="
+                  line-height: 32px;
+                  margin-right: 8px;
+                  padding: 0 8px;
+                  background-color: #eeeeee;
+                  border-radius: 4px;
+                  cursor: pointer;
+                " @click="clickMore(item)" v-if="item.afterSalesRecordsList.length >= 3">
+                  更多
+                </div>
+              </template>
+            </div>
+          </div>
+        </template>
+
+      </byTable>
+    </div>
+
+    <el-dialog title="添加售后" v-model="dialogVisible" width="70%" destroy-on-close v-if="dialogVisible">
+      <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="formDom" v-loading="submitLoading">
+        <template #btn>
+          <div style="width:100%;display:flex">
+            <div style="width:calc(100% - 105px)">
+              <el-form-item label="订单号" prop="contractCode" class="margin-b-0">
+                <el-input disabled v-model="formData.data.contractCode" placeholder="请选择"></el-input>
+              </el-form-item>
+            </div>
+            <el-button type="primary" style="width:88px;margin-left:15px" @click="openMaterial = true" plain>选择产品</el-button>
+          </div>
+        </template>
+
+        <template #image>
+          <div style="width:100%">
+            <img v-if="formData.data.fileUrl" :src="formData.data.fileUrl" class="pic" @click="openImg(formData.data.fileUrl)" />
+          </div>
+        </template>
+      </byForm>
+      <template #footer v-if="!formOption.disabled">
+        <el-button @click="dialogVisible = false" size="defualt" v-debounce>取 消</el-button>
+        <el-button type="primary" @click="submitForm()" size="defualt" v-debounce>
+          确 定
+        </el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="openMaterial" title="选择订单产品" width="80%" append-to-body>
+      <SelectContractProduct @selectProduct="handleSelect"></SelectContractProduct>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="openMaterial = false">取消</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <el-dialog title="添加跟进记录" v-if="openFollow" v-model="openFollow" width="500" destroy-on-close>
+      <byForm :formConfig="formConfigAFollow" :formOption="formOption" v-model="formFollow.data" :rules="rulesFollow" ref="follow">
+      </byForm>
+      <template #footer>
+        <el-button @click="openFollow = false" size="large">取 消</el-button>
+        <el-button type="primary" @click="submitFollow()" size="large">确 定</el-button>
+      </template>
+    </el-dialog>
+
+    <el-dialog title="跟进记录" v-if="openRecordMore" v-model="openRecordMore" width="800" destroy-on-close>
+      <div>
+        <!-- <div style="padding: 8px 0">
+          <el-button type="primary" @click="clickFollowUp(rowData)" plain>添加跟进记录</el-button>
+        </div> -->
+        <div style="padding-top: 16px">
+          <el-timeline>
+            <el-timeline-item v-for="(record, index) in recordList" :key="index" :timestamp="record.date" hide-timestamp>
+              <div>
+                <div style="
+                      padding: 0 0 8px 0;
+                      display: flex;
+                      justify-content: space-between;
+                    ">
+                  <span>{{
+                      dictValueLabel(record.createUser, userList)
+                    }}</span>
+                  <span>{{ record.followUpTime }}</span>
+                </div>
+                <div style="word-wrap: break-word; margin: 8px 0">
+                  {{ record.remark }}
+                </div>
+                <div style="margin: 8px 0; display: flex" v-if="record.fileList && record.fileList.length > 0">
+                  <div style="width: 36px">附件:</div>
+                  <div style="width: calc(100% - 36px)">
+                    <div v-for="(file, index) in record.fileList" :key="index">
+                      <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </el-timeline-item>
+          </el-timeline>
+        </div>
+      </div>
+      <template #footer>
+        <el-button @click="openRecordMore = false" size="large">关 闭</el-button>
+      </template>
+    </el-dialog>
+
+  </div>
+</template>
+  
+<script setup>
+import byTable from "@/components/byTable/index";
+import byForm from "@/components/byForm/index";
+import SelectContractProduct from "@/views/salesMange/afterSales/SelectContractProduct.vue";
+import $bus from "@/bus/index.js";
+import { reactive } from "vue";
+
+const { proxy } = getCurrentInstance();
+const loading = ref(false);
+const submitLoading = ref(false);
+const sourceList = ref({
+  data: [],
+  pagination: {
+    total: 3,
+    pageNum: 1,
+    pageSize: 10,
+    keyword: "",
+  },
+});
+const openMaterial = ref(false);
+const dialogVisible = ref(false);
+const rules = ref({
+  contractCode: [{ required: true, message: "请选择订单", trigger: "blur" }],
+  type: [{ required: true, message: "请选择售后类型", trigger: "change" }],
+  remark: [{ required: true, message: "请输入售后说明", trigger: "blur" }],
+  contactName: [
+    { required: true, message: "请输入客户联系人", trigger: "blur" },
+  ],
+  contactWay: [
+    { required: true, message: "请输入客户联系方式", trigger: "blur" },
+  ],
+  userId: [{ required: true, message: "请选择售后人员", trigger: "change" }],
+  quantity: [{ required: true, message: "请输入售后数量", trigger: "blur" }],
+});
+const userList = ref([]);
+const afterSalesType = computed(
+  () => proxy.useUserStore().allDict["after_sales_type"]
+);
+const statusData = ref([
+  {
+    label: "草稿",
+    value: 0,
+  },
+  {
+    label: "审批中",
+    value: 10,
+  },
+  {
+    label: "驳回",
+    value: 20,
+  },
+  {
+    label: "审批通过",
+    value: 30,
+  },
+  // {
+  //   label: "变更中",
+  //   value: 60,
+  // },
+  // {
+  //   label: "已变更",
+  //   value: 70,
+  // },
+  // {
+  //   label: "作废",
+  //   value: 88,
+  // },
+  {
+    label: "终止",
+    value: 99,
+  },
+]);
+const selectConfig = computed(() => [
+  {
+    label: "审批状态",
+    prop: "status",
+    data: statusData.value,
+  },
+  {
+    label: "售后类型",
+    prop: "type",
+    data: afterSalesType.value,
+  },
+]);
+const config = computed(() => {
+  return [
+    {
+      attrs: {
+        label: "订单号",
+        prop: "contractCode",
+        width: 150,
+        fixed: "left",
+      },
+    },
+    {
+      attrs: {
+        label: "审批状态",
+        prop: "status",
+        width: 120,
+        fixed: "left",
+      },
+      render(type) {
+        return proxy.dictValueLabel(type, statusData.value);
+      },
+    },
+    {
+      attrs: {
+        label: "售后类型",
+        prop: "type",
+        width: 80,
+        fixed: "left",
+      },
+      render(val) {
+        return proxy.dictKeyValue(val, afterSalesType.value);
+      },
+    },
+    {
+      attrs: {
+        label: "售后原因",
+        prop: "reason",
+        width: 150,
+      },
+    },
+    {
+      attrs: {
+        label: "售后说明",
+        prop: "remark",
+        "min-width": 150,
+      },
+    },
+    {
+      attrs: {
+        label: "售后金额",
+        prop: "amount",
+        slot: "amount",
+        width: 100,
+      },
+      render(val) {
+        return proxy.moneyFormat(val, 2);
+      },
+    },
+    // {
+    //   attrs: {
+    //     label: "图片",
+    //     slot: "pic",
+    //     align: "center",
+    //     width: 80,
+    //   },
+    // },
+    {
+      attrs: {
+        label: "初判责任方",
+        prop: "dutyParty",
+        width: 100,
+      },
+    },
+    {
+      attrs: {
+        label: "客户联系人",
+        prop: "contactName",
+        width: 100,
+      },
+    },
+    {
+      attrs: {
+        label: "客户联系电话",
+        prop: "contactTel",
+        width: 150,
+      },
+    },
+    {
+      attrs: {
+        label: "开户行",
+        prop: "accountBank",
+        width: 110,
+      },
+    },
+    {
+      attrs: {
+        label: "账户名",
+        prop: "accountName",
+        width: 110,
+      },
+    },
+    {
+      attrs: {
+        label: "账号",
+        prop: "accountNumber",
+        width: 150,
+      },
+    },
+    {
+      attrs: {
+        label: "跟进",
+        slot: "follow",
+        "min-width": 440,
+      },
+    },
+    {
+      attrs: {
+        label: "操作",
+        width: 160,
+        align: "center",
+        fixed: "right",
+      },
+      renderHTML(row) {
+        return [
+          row.status == 0
+            ? {
+                attrs: {
+                  label: "修改",
+                  type: "primary",
+                  text: true,
+                },
+                el: "button",
+                click() {
+                  clickUpdate(row);
+                },
+              }
+            : {},
+          row.status == 0
+            ? {
+                attrs: {
+                  label: "删除",
+                  type: "danger",
+                  text: true,
+                },
+                el: "button",
+                click() {
+                  proxy
+                    .msgConfirm()
+                    .then((res) => {
+                      proxy
+                        .post("/afterSales/delete", {
+                          id: row.id,
+                        })
+                        .then((res) => {
+                          proxy.msgTip("操作成功", 1);
+                          getList();
+                        });
+                    })
+                    .catch((err) => {});
+                },
+              }
+            : {},
+          // row.status == 30
+          //   ? {
+          //       attrs: {
+          //         label: "作废",
+          //         type: "danger",
+          //         text: true,
+          //       },
+          //       el: "button",
+          //       click() {
+          //         proxy
+          //           .msgConfirm()
+          //           .then((res) => {
+          //             proxy
+          //               .post("/contract/delete", {
+          //                 id: row.id,
+          //               })
+          //               .then((res) => {
+          //                 proxy.msgTip("操作成功", 1);
+          //                 getList();
+          //               });
+          //           })
+          //           .catch((err) => {});
+          //       },
+          //     }
+          //   : {},
+          row.status == 30
+            ? {
+                attrs: {
+                  label: "跟进",
+                  type: "primary",
+                  text: true,
+                },
+                el: "button",
+                click() {
+                  clickFollowUp(row);
+                },
+              }
+            : {},
+        ];
+      },
+    },
+  ];
+});
+const formData = reactive({
+  data: {
+    afterSalesDetailList: [],
+  },
+  followData: {},
+  detailData: {},
+});
+const formOption = reactive({
+  inline: true,
+  labelWidth: 110,
+  itemWidth: 100,
+  rules: [],
+});
+const formConfig = computed(() => {
+  return [
+    {
+      type: "title1",
+      title: "售后产品",
+    },
+    {
+      type: "slot",
+      slotName: "btn",
+      // prop: "code",
+      label: "",
+      itemWidth: 50,
+      disabled: true,
+    },
+    {
+      type: "number",
+      prop: "quantity",
+      label: "售后数量",
+      precision: 0,
+      min: 0,
+      controls: false,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "productCode",
+      label: "产品编码",
+      itemWidth: 50,
+      disabled: true,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "productName",
+      label: "产品名称",
+      itemWidth: 50,
+      disabled: true,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "size",
+      label: "规格尺寸 (cm)",
+      itemWidth: 50,
+      disabled: true,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "productColor",
+      label: "颜色",
+      itemWidth: 50,
+      disabled: true,
+    },
+    {
+      type: "slot",
+      slotName: "image",
+      label: "产品图片",
+      itemWidth: 100,
+    },
+    {
+      type: "title1",
+      title: "基本信息",
+    },
+    {
+      type: "select",
+      prop: "type",
+      label: "售后类型",
+      required: true,
+      itemWidth: 50,
+      data: afterSalesType.value,
+      disabled: false,
+    },
+    {
+      type: "select",
+      prop: "userId",
+      label: "售后人员",
+      itemWidth: 50,
+      filterable: true,
+      data: userList.value,
+      disabled: false,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "contactName",
+      label: "客户联系人",
+      disabled: false,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "text",
+      prop: "contactWay",
+      label: "客户联系方式",
+      disabled: false,
+      itemWidth: 50,
+    },
+    {
+      type: "input",
+      itemType: "textarea",
+      prop: "remark",
+      label: "售后说明",
+      required: true,
+      itemWidth: 50,
+    },
+  ];
+});
+
+const formDom = ref(null);
+
+const getList = (req) => {
+  sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
+  loading.value = true;
+  proxy.post("/afterSales/page", sourceList.value.pagination).then((res) => {
+    sourceList.value.data = res.rows;
+    sourceList.value.pagination.total = res.total;
+    setTimeout(() => {
+      loading.value = false;
+    }, 200);
+  });
+};
+
+const submitForm = () => {
+  formDom.value.handleSubmit(() => {
+    submitLoading.value = true;
+    proxy.post("/afterSales/add", formData.data).then(
+      (res) => {
+        proxy.msgTip("操作成功");
+        dialogVisible.value = false;
+        submitLoading.value = false;
+        getList();
+      },
+      (err) => {
+        submitLoading.value = false;
+      }
+    );
+  });
+};
+
+const getDict = () => {
+  proxy
+    .get("/tenantUser/list", {
+      pageNum: 1,
+      pageSize: 10000,
+      companyId: proxy.useUserStore().user.companyId,
+    })
+    .then((res) => {
+      userList.value = res.rows.map((item) => {
+        return {
+          label: item.nickName,
+          value: item.userId,
+        };
+      });
+    });
+};
+getDict();
+getList();
+const clickAdd = (type) => {
+  // formOption.disabled = false;
+  // formData.data = {};
+  // dialogVisible.value = true;
+  proxy.$router.replace({
+    path: "/platform_manage/process/processApproval",
+    query: {
+      flowKey: "after_sales_flow",
+      flowName: "售后流程",
+      random: proxy.random(),
+    },
+  });
+};
+
+const handleSelect = (row) => {
+  formData.data = {
+    contractCode: row.contractCode,
+    productCode: row.productCode,
+    productName: row.productName,
+    productColor: row.productColor,
+    size: `${row.productLength}*${row.productWidth}*${row.productHeight}`,
+    contractId: row.contractId,
+    type: "",
+    userId: proxy.useUserStore().user.userId,
+    contactName: "",
+    contactWay: "",
+    remark: "",
+    status: "",
+    contractProductId: row.id,
+    productId: row.productId,
+    quantity: null,
+  };
+  proxy
+    .post("/fileInfo/getList", { businessIdList: [row.productId] })
+    .then((res) => {
+      if (res[row.productId] && res[row.productId].length > 0) {
+        let list = res[row.productId].filter((x) => x.businessType == "0");
+        console.log(list);
+        if (list && list.length > 0) {
+          formData.data.fileUrl = list[0].fileUrl;
+        }
+      }
+    });
+  openMaterial.value = false;
+  proxy.msgTip("选择成功");
+};
+
+const getDtl = (row) => {
+  proxy.$router.push({
+    path: "/platform_manage/process/processApproval",
+    query: {
+      flowKey: "after_sales_flow",
+      id: row.flowId,
+      processType: 20,
+      businessId: row.id,
+    },
+  });
+};
+
+onMounted(() => {
+  $bus.on("refreshTableData", () => {
+    getList();
+  });
+});
+
+onBeforeUnmount(() => {
+  // 取消订阅特定事件
+  $bus.off("refreshTableData");
+});
+
+const clickUpdate = (row) => {
+  proxy.$router.replace({
+    path: "/platform_manage/process/processApproval",
+    query: {
+      flowKey: "after_sales_flow",
+      businessId: row.id,
+      flowName: "售后流程",
+      random: proxy.random(),
+    },
+  });
+};
+
+const getStyle = (val) => {
+  if (val) {
+    return "跟进记录: " + val.replace(/\n|\r\n/g, "<br>");
+  } else {
+    return "";
+  }
+};
+const getContent = (item) => {
+  if (item.type === "10") {
+    return "跟进记录: " + "报价单总金额 " + proxy.moneyFormat(item.amount, 2);
+  } else if (item.type === "20") {
+    return (
+      "跟进记录: " +
+      "合同总金额 " +
+      proxy.moneyFormat(item.amount, 2) +
+      ` (${item.contractCode}) `
+    );
+  }
+};
+
+const formConfigAFollow = computed(() => {
+  return [
+    {
+      type: "date",
+      itemType: "datetime",
+      label: "跟进时间",
+      prop: "followUpTime",
+      itemWidth: 100,
+    },
+    {
+      type: "input",
+      itemType: "textarea",
+      label: "跟进内容",
+      prop: "remark",
+      itemWidth: 100,
+    },
+    {
+      type: "upload",
+      listType: "text",
+      accept: "",
+      prop: "fileList",
+      label: "上传附件",
+    },
+  ];
+});
+const rulesFollow = ref({
+  followUpTime: [
+    { required: true, message: "请选择跟进时间", trigger: "change" },
+  ],
+  remark: [{ required: true, message: "请输入跟进内容", trigger: "blur" }],
+});
+const formFollow = reactive({
+  data: {},
+});
+const follow = ref(null);
+const openFollow = ref(false);
+const openRecordMore = ref(false);
+const clickFollowUp = (item) => {
+  formFollow.data = {
+    afterSalesId: item.id,
+    fileList: [],
+  };
+
+  openFollow.value = true;
+  openRecordMore.value = false;
+};
+const submitFollow = () => {
+  follow.value.handleSubmit(() => {
+    proxy.post("/afterSalesRecords/add", formFollow.data).then(
+      () => {
+        proxy.msgTip("添加成功");
+        openFollow.value = false;
+        getList();
+      },
+      (err) => {
+        console.log(err);
+      }
+    );
+  });
+};
+
+const recordShow = (item) => {
+  if (
+    !(item.fileList && item.fileList.length > 0) &&
+    JSON.stringify(item.fileList) !== "[]"
+  ) {
+    proxy
+      .post("/fileInfo/getList", { businessIdList: [item.id] })
+      .then((fileObj) => {
+        item.fileList = fileObj[item.id] || [];
+      });
+  }
+};
+
+const deleteFollow = (data) => {
+  proxy
+    .msgConfirm()
+    .then((res) => {
+      proxy
+        .post("/afterSalesRecords/delete", {
+          id: data.id,
+        })
+        .then(() => {
+          proxy.msgTip("删除成功", 1);
+          getList();
+        });
+    })
+    .catch((err) => {});
+};
+const queryParams = ref({
+  total: 0,
+  pageNum: 1,
+  pageSize: 10,
+  afterSalesDetailId: "",
+});
+const recordList = ref([]);
+const rowData = ref({});
+const clickMore = (item) => {
+  recordList.value = proxy.deepClone(item.afterSalesRecordsList);
+  let ids = recordList.value.map((x) => x.id);
+  proxy.getFileData({
+    businessIdList: ids,
+    data: recordList.value,
+    att: "id",
+    businessType: "0",
+    fileAtt: "fileList",
+    filePathAtt: "fileUrl",
+  });
+  openRecordMore.value = true;
+};
+const infiniteScroll = () => {
+  queryParams.value.pageNum++;
+  clickMore();
+};
+const judgeTotal = () => {
+  if (
+    queryParams.value.pageNum * queryParams.value.pageSize >=
+    queryParams.value.total
+  ) {
+    return true;
+  }
+  return false;
+};
+const openFile = (path) => {
+  window.open(path, "_blank");
+};
+</script>
+  
+<style lang="scss" scoped>
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 131 - 548
src/views/salesMange/shipmentMange/packing/index.vue


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است