From 66b41779cf4090ed86509d9ed4b3522ded8e9049 Mon Sep 17 00:00:00 2001
From: he wei <858544502@qq.com>
Date: 星期四, 25 七月 2024 13:30:57 +0800
Subject: [PATCH] UA 不良品页面提交

---
 src/pages/components/filesList.vue                   |  179 +++++
 src/pages/resourceManage/defective/apis.js           |   48 +
 src/pages/resourceManage/defective/list.vue          |  932 +++++++++++++++++++++++++++
 src/pages/resourceManage/defective/prodUpload.vue    |  417 ++++++++++++
 src/components/datetimeRange/datetimeRange.vue       |   20 
 src/pages/resourceManage/defective/disposeUpload.vue |  389 +++++++++++
 src/pages/resourceManage/defective/index.js          |    2 
 src/router/config.js                                 |    5 
 src/components/table/advance/SearchArea.vue          |    7 
 9 files changed, 1,996 insertions(+), 3 deletions(-)

diff --git a/src/components/datetimeRange/datetimeRange.vue b/src/components/datetimeRange/datetimeRange.vue
index 76cc4a2..5acaab5 100644
--- a/src/components/datetimeRange/datetimeRange.vue
+++ b/src/components/datetimeRange/datetimeRange.vue
@@ -1,11 +1,11 @@
 <template>
   <div>
     <a-date-picker :getCalendarContainer="getCalendarContainer" v-model="startValue" :disabled-date="disabledStartDate"
-      :disabled-time="disabledStartTime" show-time format="YYYY-MM-DD HH:mm:ss" placeholder="Start" :open="startOpen"
+      :disabled-time="disabledStartTime" :show-time="showTime" :format="formatStr" placeholder="Start" :open="startOpen"
       size="small" @openChange="handleStartOpenChange" @change="change(0)" />
     -
     <a-date-picker :getCalendarContainer="getCalendarContainer" v-model="endValue" :disabled-date="disabledEndDate"
-      :disabled-time="disabledEndTime" show-time format="YYYY-MM-DD HH:mm:ss" placeholder="End" :open="endOpen"
+      :disabled-time="disabledEndTime" :show-time="showTime" :format="formatStr" placeholder="End" :open="endOpen"
       size="small" @change="change(1)" @openChange="handleEndOpenChange" />
   </div>
 </template>
@@ -20,7 +20,18 @@
       endOpen: false,
     };
   },
-  props: ["value", "getCalendarContainer"],
+  props: {
+    value: {
+      required: true,
+    },
+    getCalendarContainer: {
+      required: true
+    },
+    showTime: {
+      type: Boolean,
+      default: true
+    },
+  },
   model: {
     prop: "value",
     event: "change",
@@ -40,6 +51,9 @@
         this.endValue = value[1];
       },
     },
+    formatStr() {
+      return this.showTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD"
+    },
   },
   methods: {
     range(start, end) {
diff --git a/src/components/table/advance/SearchArea.vue b/src/components/table/advance/SearchArea.vue
index 8bc7772..25377bb 100644
--- a/src/components/table/advance/SearchArea.vue
+++ b/src/components/table/advance/SearchArea.vue
@@ -41,6 +41,13 @@
         <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
         <datetime-range v-model="col.search.value" :getCalendarContainer="() => $refs.root" @change="onDateRangeChange(col)"></datetime-range>
       </div>
+      <div v-else-if="col.dataType === 'dateRange'" :class="['title', {active: col.search.value && (col.search.value[0] || col.search.value[1])}]">
+        <template v-if="col.title">
+          {{col.title}}:
+        </template>
+        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
+        <datetime-range v-model="col.search.value" :showTime="false" :getCalendarContainer="() => $refs.root" @change="onDateRangeChange(col)"></datetime-range>
+      </div>
       <div v-else-if="col.dataType === 'select'" :class="['title', {active: col.search.value !== undefined}]">
         <template v-if="col.title">
           {{col.title}}:
diff --git a/src/pages/components/filesList.vue b/src/pages/components/filesList.vue
new file mode 100644
index 0000000..9e6e45a
--- /dev/null
+++ b/src/pages/components/filesList.vue
@@ -0,0 +1,179 @@
+<template>
+  <div class="">
+    <a-table
+      ref="aTable"
+      size="small"
+      :scroll="{ y }"
+      bordered
+      :columns="columns"
+      :data-source="dataSource"
+      :pagination="false"
+      :rowKey="(record, index) => index"
+    >
+      <template slot="action" slot-scope="text, record">
+        <div v-if="record.url">
+          <a v-if="canView(record)" @click="view(record)">棰勮</a>
+          <template>
+            <a-divider type="vertical"></a-divider>
+            <a @click="downloadOther(record.url)">涓嬭浇</a>
+          </template>
+        </div>
+      </template>
+    </a-table>
+    <a-modal
+      :width="600"
+      :visible="previewVisible"
+      :footer="null"
+      @cancel="handleCancel"
+    >
+      <img alt="example" style="width: 100%" :src="imgUrl" />
+    </a-modal>
+  </div>
+</template>
+
+<script>
+import getWebUrl from "@/assets/js/tools/getWebUrl";
+import { dwgReview } from "@/pages/workplace/apis";
+import { mapGetters } from "vuex";
+export default {
+  name: "",
+  components: {},
+  props: {
+    list: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
+  data() {
+    const columns = [
+      {
+        title: "鏂囦欢鍚嶇О",
+        dataIndex: "fileName",
+        align: "center",
+        width: 200,
+      },
+      {
+        title: "鏂囦欢绫诲瀷",
+        dataIndex: "fileType",
+        align: "center",
+        width: 100,
+      },
+      {
+        title: "鎿嶄綔",
+        dataIndex: "operation",
+        key: "operation",
+        align: "center",
+        width: 200,
+        scopedSlots: { customRender: "action" },
+      },
+    ];
+    return {
+      y1: 400,
+      columns,
+      y: 500,
+      imgUrl: "",
+      previewVisible: false,
+      webUrl: getWebUrl(),
+    };
+  },
+  methods: {
+    handleClick() {
+      this.$emit("success", this.list);
+    },
+    canView(obj) {
+      return [
+        "bmp",
+        "jpg",
+        "jpeg",
+        "gif",
+        "png",
+        "pdf",
+        "doc",
+        "docx",
+        "dwg",
+      ].some((v) => v == obj.fileType);
+    },
+    view(obj) {
+      switch (obj.fileType) {
+        // 鍥剧墖
+        case "bmp":
+        case "jpg":
+        case "jpeg":
+        case "gif":
+        case "png":
+          this.imgUrl = this.webUrl + obj.url;
+          this.previewVisible = true;
+          break;
+        case "pdf":
+          window.open(this.webUrl + obj.url);
+          break;
+        case "doc":
+        case "docx":
+        case "dwg":
+          this.dwgReview(obj.url);
+          break;
+        default:
+          this.$message.warn("璇ョ被鍨嬫枃浠舵殏涓嶆敮鎸侀瑙�");
+          break;
+      }
+    },
+    handleCancel() {
+      this.previewVisible = false;
+    },
+    dwgReview(url) {
+      let loading = this.$layer.loading();
+      dwgReview(url)
+        .then((res) => {
+          let { code, data, msg } = res.data;
+          if (code && data) {
+            window.open(this.webUrl + data);
+          } else {
+            this.$message.error(msg);
+          }
+          this.$layer.close(loading);
+        })
+        .catch((error) => {
+          console.log(error);
+          this.$layer.close(loading);
+        });
+    },
+    downloadOther(url) {
+      let reg = /(.*\\+)*(.*)$/;
+      let fileName = url.match(reg)[2];
+
+      let link = document.createElement("a");
+      link.style.display = "none";
+      link.href = this.webUrl + url;
+      link.download = fileName;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+    },
+  },
+  computed: {
+    dataSource() {
+      let reg = /(.*\\+)*(.*)$/;
+      return this.list.map((item) => {
+        let fileName = item.match(reg)[2];
+        let arr = fileName.split(".");
+        let fileType = arr.length ? arr[arr.length - 1].toLowerCase() : "";
+        return {
+          fileName,
+          fileType,
+          url: item,
+        };
+      });
+    },
+    ...mapGetters("account", ["permits"]),
+  },
+  mounted() {},
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.is-lock td {
+  background: #ddd;
+}
+</style>
diff --git a/src/pages/resourceManage/defective/apis.js b/src/pages/resourceManage/defective/apis.js
new file mode 100644
index 0000000..d1ea008
--- /dev/null
+++ b/src/pages/resourceManage/defective/apis.js
@@ -0,0 +1,48 @@
+import axios from "@/assets/axios";
+
+/**
+ * 褰曞叆涓嶈壇鍝佷俊鎭�
+ * @param {*} data {multipartFileList, defectiveJson}
+ * @returns 
+ */
+export const addDefective = (data) => {
+  return axios({
+    method: "POST",
+    url: "defective/addDefective",
+    headers: {
+      "Content-Type": "multipart/form-data"
+    },
+    data
+  })
+}
+
+/**
+ * 澶勭悊涓嶈壇鍝佷俊鎭�
+ * @param {*} data {multipartFileList, defectiveHisJson}
+ * @returns 
+ */
+export const updateDefective = (data) => {
+  return axios({
+    method: "POST",
+    url: "defective/updateDefective",
+    headers: {
+      "Content-Type": "multipart/form-data"
+    },
+    data
+  })
+}
+
+/**
+ * 褰掓。涓嶈壇鍝佷俊鎭�
+ * @param {*} data deftId
+ * @returns 
+ */
+export const stopDefective = (deftId) => {
+  return axios({
+    method: "POST",
+    url: "defective/stopDefective",
+    params: {
+      deftId
+    }
+  })
+}
\ No newline at end of file
diff --git a/src/pages/resourceManage/defective/disposeUpload.vue b/src/pages/resourceManage/defective/disposeUpload.vue
new file mode 100644
index 0000000..dcd88ba
--- /dev/null
+++ b/src/pages/resourceManage/defective/disposeUpload.vue
@@ -0,0 +1,389 @@
+<template>
+  <div class="dispose">
+    <a-form-model
+      ref="formRef"
+      name="advanced_search"
+      class="ant-advanced-search-form"
+      :model="info"
+      :rules="rules"
+    >
+      <a-row>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="鍏ュ簱鏃堕棿"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="startTime"
+          >
+            <a-date-picker
+              format="YYYY-MM-DD"
+              disabled
+              valueFormat="YYYY-MM-DD"
+              :allowClear="false"
+              :disabled-date="disabledDate"
+              :show-time="{ defaultValue: moment('00:00:00', 'HH:mm:ss') }"
+              v-model="info.startTime"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="鍘傚晢鍚嶇О"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="provideName"
+          >
+            <a-input
+              disabled
+              placeholder="璇疯緭鍏ュ巶鍟嗗悕绉�"
+              v-model.trim="info.provideName"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="浜у搧鍚嶇О"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="productName"
+          >
+            <a-input
+              disabled
+              placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+              v-model.trim="info.productName"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="浜у搧鍨嬪彿"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="type"
+          >
+            <a-input
+              disabled
+              placeholder="璇疯緭鍏ヤ骇鍝佸瀷鍙�"
+              v-model.trim="info.type"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="鍓╀綑涓嶈壇鏁�"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="restProduct"
+          >
+            <a-input disabled v-model.number="info.restProduct" />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="澶勭悊鏁伴噺"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="delProduct"
+          >
+            <a-input-number
+              placeholder="璇疯緭鍏ュ鐞嗘暟閲�"
+              :min="1"
+              :max="info.restProduct"
+              v-model.number="info.delProduct"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="24">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="澶勭悊鎻忚堪"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="note"
+          >
+            <a-textarea
+              placeholder="璇疯緭鍏ュ鐞嗘弿杩�"
+              v-model.trim="info.note"
+              :rows="2"
+            />
+          </a-form-model-item>
+        </a-col>
+      </a-row>
+      <a-row type="flex" class="row">
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="閭欢閫氱煡"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="selectedItems"
+          >
+            <a-select
+              mode="multiple"
+              placeholder=""
+              :value="info.selectedItems"
+              style="width: 100%"
+              allow-clear
+              @change="handleChange"
+            >
+              <a-select-option
+                v-for="item in filteredOptions"
+                :key="item.name"
+                :value="item.name"
+                :disabled="!item.isHasMail"
+              >
+                {{ item.name }}
+              </a-select-option>
+            </a-select>
+          </a-form-model-item>
+        </a-col>
+      </a-row>
+      <a-row type="flex" class="row">
+        <a-col flex="8em" class="label">闄勪欢</a-col>
+        <a-col :flex="1">
+          <a-upload
+            class="upload"
+            :before-upload="beforeUpload"
+            @change="uploadChange"
+            multiple
+          >
+            <!-- accept=".zip,.rar" -->
+            <a-button type="primary">閫夋嫨闄勪欢</a-button>
+          </a-upload>
+        </a-col>
+      </a-row>
+    </a-form-model>
+    <div class="modal-footer">
+      <a-button type="danger" @click="cancel"> 鍙栨秷 </a-button>
+      <a-button type="primary" @click="ok"> 鎻愪氦 </a-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import moment from "moment";
+import { updateDefective } from "./apis";
+
+import { searchDefaultMailUser } from "../../components/emailCard/apis";
+import { getUserList } from "../../permission/apis";
+
+export default {
+  name: "",
+  props: {
+    record: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    // console.log("record", this.record, "=============");
+    const {
+      restProduct,
+      id: deftId,
+      productName,
+      provideName,
+      type,
+      startTime,
+    } = this.record;
+
+    return {
+      userList: [],
+      defaultMailUsers: [],
+      selectedItems: [],
+      userListAll: [],
+      info: {
+        deftId,
+        restProduct,
+        provideName,
+        productName,
+        note: "",
+        type,
+        sumProduct: "",
+        badProduct: "",
+        delProduct: "",
+        // startTime: moment().format("YYYY-MM-DD"),
+        startTime,
+        selectedItems: [],
+      },
+      file: null,
+      receiverIds: "",
+      receiverNames: "",
+      rules: {
+        delProduct: [
+          {
+            required: true,
+            message: "璇疯緭鍏ュ鐞嗘暟閲�",
+            trigger: "blur",
+          },
+        ],
+        note: [
+          {
+            required: true,
+            message: "璇疯緭鍏ュ鐞嗘弿杩�",
+            trigger: "blur",
+          },
+        ],
+        selectedItems: [
+          {
+            required: true,
+            message: "璇峰厛鎷╄閭欢閫氱煡鐨勪汉鍛�",
+            trigger: "blur",
+          },
+          // { validator: validateSelectedItems, trigger: "change" },
+        ],
+      },
+    };
+  },
+  computed: {
+    filteredOptions() {
+      let users = this.userListAll;
+      return users
+        .filter((o) => !this.info.selectedItems.includes(o.name))
+        .map((o) => {
+          o.isHasMail = o.mail ? true : false;
+          return o;
+        });
+    },
+  },
+  methods: {
+    moment,
+    beforeUpload() {
+      return false;
+    },
+    uploadChange(data) {
+      const { file, fileList } = data;
+      if (fileList.length) {
+        this.file = fileList.map((v) => v.originFileObj);
+      } else {
+        this.file = null;
+      }
+    },
+    searchAllUserList() {
+      getUserList()
+        .then((res) => {
+          let rs = res.data;
+          if (rs.code && rs.data) {
+            this.userListAll = rs.data2;
+            this.searchDefaultMailUser();
+          }
+        })
+        .catch((error) => {
+          console.log(error);
+        });
+    },
+    searchDefaultMailUser() {
+      //  type涓�4
+      let type = 4;
+      searchDefaultMailUser(type).then((res) => {
+        let rs = res.data;
+        let data = [];
+        if (rs.code === 1) {
+          data = rs.data.map((item) => {
+            return item.user;
+          });
+        }
+        this.info.selectedItems = this.userListAll
+          .filter((o) => data.includes(o.name) && o.mail)
+          .map((item) => {
+            return item.name;
+          });
+        this.handleChange(this.info.selectedItems);
+      });
+    },
+    handleChange(selectedItems) {
+      this.info.selectedItems = selectedItems;
+      let users = this.userListAll;
+      let mailList = users.filter((o) =>
+        this.info.selectedItems.includes(o.name)
+      );
+      this.receiverNames = mailList.map((v) => v.name).join(",");
+      this.receiverIds = mailList.map((v) => v.id).join(",");
+    },
+    cancel() {
+      this.$emit("cancel");
+    },
+    ok() {
+      this.$refs["formRef"].validate((valid) => {
+        if (valid) {
+          let loading = this.$layer.loading();
+          const formData = new FormData();
+          if (this.file) {
+            this.file.forEach((v) => {
+              formData.append("multipartFileList", v);
+            });
+          }
+          let { deftId, note, delProduct } = this.info;
+          formData.append(
+            "defectiveHisJson",
+            JSON.stringify({
+              deftId,
+              note,
+              delProduct,
+              receiverIds: this.receiverIds,
+              receiverNames: this.receiverNames,
+            })
+          );
+
+          updateDefective(formData)
+            .then((res) => {
+              let { code, data } = res.data;
+              this.$layer.close(loading);
+              if (code && data) {
+                this.$message.success("鎿嶄綔鎴愬姛");
+                this.$emit("ok");
+              } else {
+                this.$message.error("鎿嶄綔澶辫触");
+              }
+            })
+            .catch((err) => {
+              console.log(err);
+              this.$layer.close(loading);
+            });
+        } else {
+          this.$message.error("瀛樺湪鏈�氳繃妫�楠岀殑琛ㄥ崟椤�");
+          return false;
+        }
+      });
+    },
+    disabledDate(current) {
+      // Can not select days before today and today
+      return current > moment().endOf("day");
+    },
+  },
+  mounted() {
+    this.searchAllUserList();
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.modal-footer {
+  text-align: right;
+  button + button {
+    margin-left: 8px;
+  }
+}
+.row {
+  color: rgba(0, 0, 0, 0.85);
+  line-height: 30px;
+  .label {
+    text-align: right;
+  }
+  .label::after {
+    content: ":";
+    position: relative;
+    top: -0.5px;
+    margin: 0 8px 0 2px;
+  }
+}
+
+/deep/ .ant-input-number {
+  width: 100%;
+}
+</style>
diff --git a/src/pages/resourceManage/defective/index.js b/src/pages/resourceManage/defective/index.js
new file mode 100644
index 0000000..5d44f9c
--- /dev/null
+++ b/src/pages/resourceManage/defective/index.js
@@ -0,0 +1,2 @@
+import list from './list';
+export default list;
diff --git a/src/pages/resourceManage/defective/list.vue b/src/pages/resourceManage/defective/list.vue
new file mode 100644
index 0000000..cd7a070
--- /dev/null
+++ b/src/pages/resourceManage/defective/list.vue
@@ -0,0 +1,932 @@
+<template>
+  <div class="main">
+    <div class="inner" ref="wraper">
+      <a-spin class="" :spinning="spinning" tip="鎷煎懡鍔犺浇涓�...">
+        <a-card>
+          <advance-table
+            ref="table"
+            class="doc-center-table"
+            :data-source="dataSource"
+            :columns="columns"
+            :loading="loading"
+            title=""
+            row-key="id"
+            @search="onSearch"
+            @refresh="onRefresh"
+            @reset="onReset"
+            :format-conditions="true"
+            :scroll="{ x: 400, y }"
+            :pagination="{
+              current: pageCurr,
+              pageSize: pageSize,
+              total: total,
+              showSizeChanger: true,
+              showLessItems: true,
+              showQuickJumper: true,
+              pageSizeOptions: ['10', '20', '50', '100'],
+              showTotal: (total, range) =>
+                `绗� ${range[0]}-${range[1]} 鏉★紝鎬昏 ${total} 鏉,
+              onChange: onPageChange,
+              onShowSizeChange: onSizeChange,
+            }"
+            :rowClassName="rowClassFn"
+          >
+            <template slot="dataIndex" slot-scope="{ index }">
+              {{ index + 1 }}
+            </template>
+            <template slot="title">
+              <a-space class="operator">
+                <span class="title">涓嶈壇鍝佷腑蹇�</span>
+                <a-button
+                  v-if="canUploadBom"
+                  type="primary"
+                  @click="uploadDefective"
+                  >鏂板</a-button
+                >
+              </a-space>
+            </template>
+            <template slot="action" slot-scope="{ record }">
+              <a @click="viewLog(record)">澶勭悊璁板綍</a>
+              <template v-if="record.nameList && record.nameList.length">
+                <a-divider type="vertical"></a-divider>
+                <a @click="showFileList(record)">闄勪欢</a>
+              </template>
+              <a-divider type="vertical"></a-divider>
+              <!-- TODO -->
+              <a v-if="record.confirmStatus < 2" @click="dispose(record)"
+                >澶勭悊</a
+              >
+              <a
+                v-else-if="record.confirmStatus == 2"
+                @click="stopDefective(record)"
+                >褰掓。</a
+              >
+            </template>
+          </advance-table>
+        </a-card>
+      </a-spin>
+    </div>
+    <!-- 涓婁紶 -->
+    <a-modal
+      :visible="bomUploadShow"
+      :width="760"
+      title="涓婁紶涓嶈壇鍝佷俊鎭�"
+      :destroyOnClose="true"
+      :maskClosable="false"
+      @cancel="bomUploadCancel"
+      @ok="bomUploadOk"
+      :footer="null"
+    >
+      <prod-upload @ok="bomUploadOk" @cancel="bomUploadCancel"></prod-upload>
+    </a-modal>
+    <!-- 澶勭悊 -->
+    <a-modal
+      :visible="disposeVisible"
+      :width="760"
+      title="澶勭悊涓嶈壇鍝�"
+      :destroyOnClose="true"
+      :maskClosable="false"
+      @cancel="disposeCancel"
+      @ok="disposeOk"
+      :footer="null"
+    >
+      <dispose-upload
+        :record="currentData"
+        @ok="disposeOk"
+        @cancel="disposeCancel"
+      ></dispose-upload>
+    </a-modal>
+    <!-- 鎿嶄綔璁板綍 -->
+    <a-modal
+      :visible="logVisible"
+      :width="760"
+      title="鎿嶄綔璁板綍"
+      :destroyOnClose="true"
+      :maskClosable="false"
+      @cancel="logVisible = false"
+      :footer="null"
+    >
+      <div class="log-content" v-if="logVisible">
+        <a-timeline>
+          <a-timeline-item
+            v-for="(item, idx) in logList"
+            :key="'log_' + idx"
+            :color="
+              0 == item.confirmStatus || 3 == item.confirmStatus
+                ? 'red'
+                : 'green'
+            "
+          >
+            <div>
+              <span class="user">{{ item.delName }}</span> 鍦�
+              <span class="time">{{ item.recordTime }}</span>
+              <template v-if="0 == item.confirmStatus">
+                鍒涘缓浜嗕笉鑹搧璁板綍
+                <span class="rest">浣�({{ item.restProduct }})</span>
+              </template>
+              <template v-else-if="3 == item.confirmStatus">
+                褰掓。浜嗕笉鑹搧璁板綍
+              </template>
+              <template v-else>
+                {{ { "1": "澶勭悊", "2": "澶勭悊" }[item.confirmStatus] }}浜�
+                <span class="version">{{ item.delProduct }}</span
+                >涓笉鑹搧 <span class="rest">浣�({{ item.restProduct }})</span>
+                <div class="">鎻忚堪: {{ item.note }}</div>
+                <div
+                  class="others"
+                  v-if="item.hisNameList && item.hisNameList.length"
+                >
+                  <div class="label">闄勪欢</div>
+                  <div class="content">
+                    <div
+                      class="item"
+                      v-for="file in item.hisNameList"
+                      :key="file"
+                    >
+                      <div class="file-name">{{ file }}</div>
+                      <div class="btn-list">
+                        <a-button
+                          class="btn"
+                          v-if="canView(file)"
+                          @click="view(file, item.delUrl)"
+                          >棰勮</a-button
+                        >
+                        <a-button
+                          class="btn"
+                          type="primary"
+                          @click="downloadOther(file, item.delUrl)"
+                          >涓嬭浇</a-button
+                        >
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </template>
+            </div>
+          </a-timeline-item>
+        </a-timeline>
+      </div>
+    </a-modal>
+
+    <!-- 闄勪欢 -->
+    <a-modal
+      :visible="otherFilesVisible"
+      :width="760"
+      title="闄勪欢"
+      :destroyOnClose="true"
+      :maskClosable="false"
+      @cancel="otherFilesVisible = false"
+      :footer="null"
+    >
+      <files-list v-if="otherFilesVisible" :list="fileList"></files-list>
+    </a-modal>
+    <a-modal
+      :width="600"
+      :visible="previewVisible"
+      :footer="null"
+      @cancel="handleCancel"
+    >
+      <img alt="example" style="width: 100%" :src="imgUrl" />
+    </a-modal>
+  </div>
+</template>
+
+<script>
+import AdvanceTable from "@/components/table/advance/AdvanceTable";
+import ProdUpload from "./prodUpload";
+import DisposeUpload from "./disposeUpload";
+import DrawUpload from "@/pages/components/drawUpload";
+import DownloadReason from "@/pages/components/downloadReason";
+import DownloadLogs from "@/pages/components/downloadLogs";
+
+import filesList from "@/pages/components/filesList";
+
+import getWebUrl from "@/assets/js/tools/getWebUrl";
+
+import { stopDefective } from "./apis";
+import { mapGetters } from "vuex";
+import checkPermit from "@/assets/js/tools/checkPermit";
+import PERMITS from "@/assets/js/const/const_permits";
+
+import createWs from "@/assets/js/websocket";
+import DiffList from "@/pages/components/diffList";
+import FeedbackForm from "../components/feedbackForm.vue";
+import OwnerDownload from "../components/ownerDownload";
+import moment from "moment";
+const WSMixin = createWs("defective");
+
+export default {
+  name: "",
+  mixins: [WSMixin],
+  data() {
+    return {
+      previewVisible: false,
+      imgUrl: "",
+      fileList: [],
+      otherFilesVisible: false,
+      currentData: {},
+      logVisible: false,
+      disposeVisible: false,
+      logList: [],
+
+      ownerLogVisible: false,
+      reason: "",
+      reasonVisible: false,
+      oprateInfo: "",
+      downloadlogVisible: false,
+      tester: [],
+      downloadReasonVisible: false,
+      curObj: null,
+      errorVisible: false,
+      feedbackShow: false,
+      lockListVisible: false,
+      currentObj: null,
+      bomLockList: [],
+      otherLockList: [],
+      ecrList: [],
+      ecrListVisible: false,
+      fromProd: undefined,
+      prodList: [],
+      diffData: {},
+      bomUploadShow: false,
+      prodData: {},
+      userListAll: [],
+      mailList: [],
+      file: null,
+      // title: "",
+      fileUrl: "",
+      resList: [],
+      uploadShow: false,
+      customShow: false,
+      customProd: null,
+      editShow: false,
+      editObj: undefined,
+      selectedRowKeys: [],
+      selectedRows: [],
+      spinning: false,
+      loading: false,
+      pageCurr: 1,
+      pageSize: 10,
+      total: 0,
+      y: 400,
+      update: -1,
+      webUrl: getWebUrl(),
+      conditions: {},
+      columns: [
+        {
+          fixed: "left",
+          title: "搴忓彿",
+          dataIndex: "dataIndex",
+          key: "dataIndex",
+          align: "center",
+          width: 60,
+          noSearch: true,
+          scopedSlots: { customRender: "dataIndex" },
+        },
+        {
+          title: "鐗╂枡鍚嶇О",
+          dataIndex: "productName",
+          key: "productName",
+          width: 130,
+          align: "center",
+          // searchAble: true,
+          noSearch: true,
+        },
+        {
+          title: "鍨嬪彿",
+          dataIndex: "type",
+          key: "type",
+          align: "center",
+          width: 180,
+          noSearch: true,
+        },
+        {
+          title: "鍘傚晢鍚嶇О",
+          dataIndex: "provideName",
+          key: "provideName",
+          align: "center",
+          width: 180,
+          // searchAble: true,
+          noSearch: true,
+        },
+        {
+          title: "鍏ユ枡鏃堕棿",
+          dataIndex: "startTimeEx",
+          key: "startTimeEx",
+          align: "center",
+          width: 160,
+          searchAble: true,
+          dataType: "dateRange",
+        },
+        {
+          title: "鍒涘缓浜�",
+          dataIndex: "recorder",
+          align: "center",
+          width: 160,
+          searchAble: true,
+          dataType: "select",
+          search: {
+            default: "",
+            selectOptions: [],
+          },
+        },
+        {
+          title: "鐘舵��",
+          dataIndex: "status",
+          align: "center",
+          searchAble: true,
+          dataType: "select",
+          search: {
+            default: "",
+            selectOptions: [
+              {
+                title: "鍏ㄩ儴",
+                value: "",
+              },
+              {
+                title: "鏈鐞�",
+                value: 0,
+              },
+              {
+                title: "澶勭悊涓�",
+                value: 1,
+              },
+              {
+                title: "宸插畬鎴�",
+                value: 2,
+              },
+              {
+                title: "褰掓。",
+                value: 3,
+              },
+            ],
+          },
+        },
+        {
+          title: "鎬绘暟閲�",
+          dataIndex: "sumProduct",
+          align: "center",
+          // searchAble: true,
+          noSearch: true,
+          // visible: false,
+        },
+        {
+          title: "涓嶈壇鏁�",
+          dataIndex: "badProduct",
+          align: "center",
+          // searchAble: true,
+          noSearch: true,
+          // visible: false,
+        },
+        {
+          title: "涓嶈壇鎻忚堪",
+          dataIndex: "content",
+          align: "center",
+          // searchAble: true,
+          noSearch: true,
+          // visible: false,
+        },
+        {
+          title: "鎿嶄綔",
+          dataIndex: "operation",
+          key: "operation",
+          align: "center",
+          width: 228,
+          fixed: "right",
+          scopedSlots: { customRender: "action" },
+          noSearch: true,
+        },
+      ],
+      dataSource: [],
+    };
+  },
+  components: {
+    AdvanceTable,
+    // ChangeParts,
+    filesList,
+    ProdUpload,
+    DisposeUpload,
+    DrawUpload,
+    DiffList,
+    FeedbackForm,
+    DownloadReason,
+    DownloadLogs,
+    OwnerDownload,
+  },
+  methods: {
+    rowClassFn(record) {
+      let classList = [];
+      classList.push("level-" + record.confirmStatus);
+      return classList;
+    },
+    dispose(record) {
+      this.currentData = record;
+      this.disposeVisible = true;
+    },
+    canView(fileName) {
+      let arr = fileName.split(".");
+      let fileType = arr.length ? arr[arr.length - 1].toLowerCase() : "";
+      return [
+        "bmp",
+        "jpg",
+        "jpeg",
+        "gif",
+        "png",
+        "pdf",
+        "doc",
+        "docx",
+        "dwg",
+      ].some((v) => v == fileType);
+    },
+    view(fileName, url) {
+      let arr = fileName.split(".");
+      let fileType = arr.length ? arr[arr.length - 1].toLowerCase() : "";
+      switch (fileType) {
+        // 鍥剧墖
+        case "bmp":
+        case "jpg":
+        case "jpeg":
+        case "gif":
+        case "png":
+          this.imgUrl = this.webUrl + url + fileName;
+          this.previewVisible = true;
+          break;
+        case "pdf":
+          window.open(this.webUrl + url + fileName);
+          break;
+        case "doc":
+        case "docx":
+        case "dwg":
+          this.dwgReview(url + fileName);
+          break;
+        default:
+          this.$message.warn("璇ョ被鍨嬫枃浠舵殏涓嶆敮鎸侀瑙�");
+          break;
+      }
+    },
+    handleCancel() {
+      this.previewVisible = false;
+    },
+    downloadOther(fileName, url) {
+      let link = document.createElement("a");
+      link.style.display = "none";
+      link.href = this.webUrl + url + fileName;
+      link.download = fileName;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+    },
+    stopDefective(record) {
+      // console.log("record", record, "=============");
+      const self = this;
+      this.$confirm({
+        title: "璇风‘璁�",
+        content: "璇风‘璁ゆ槸鍚﹁繘琛屽綊妗f搷浣滐紵",
+        onOk() {
+          stopDefective(record.id)
+            .then((res) => {
+              let { code, data } = res.data;
+              if (code && data) {
+                self.$message.success("鎿嶄綔鎴愬姛");
+              } else {
+                self.$message.error("鎿嶄綔澶辫触");
+              }
+            })
+            .catch((err) => {
+              console.log(err);
+            });
+        },
+        onCancel() {},
+      });
+    },
+    onSearch(conditions, searchOptions) {
+      // console.log(conditions);
+      // console.log(searchOptions);
+      this.pageCurr = 1;
+      this.conditions = conditions;
+      this.sendMessage();
+    },
+    onPageChange(page, pageSize) {
+      this.pageCurr = page;
+      this.pageSize = pageSize;
+      this.sendMessage();
+    },
+    onSizeChange(current, size) {
+      this.pageCurr = 1;
+      this.pageSize = size;
+      this.sendMessage();
+    },
+    onRefresh(conditions) {
+      this.conditions = conditions;
+      this.sendMessage();
+    },
+    onReset(conditions) {
+      this.conditions = conditions;
+      this.sendMessage();
+    },
+    showFileList(record) {
+      let { nameList, fileUrl } = record;
+      // let baseUrl = this.webUrl;
+      this.fileList = nameList.map((v) => fileUrl + v);
+      this.otherFilesVisible = true;
+    },
+    searchData(res) {
+      if (res) {
+        // res = res.data;
+        // console.log(res, "======");
+        let data = [];
+        let total = 0;
+        if (res.code && res.data) {
+          data = res.data2.list.map((v) => {
+            v.status = ["鏈鐞�", "澶勭悊涓�", "宸插畬鎴�", "褰掓。"][v.confirmStatus];
+            v.recorder = v.senderName;
+            return v;
+          });
+          total = res.data2.total;
+        }
+        this.dataSource = data;
+        this.total = total;
+        if (-1 == this.update) {
+          this.update = Math.random();
+        }
+      }
+    },
+    resize() {
+      setTimeout(() => {
+        this.update = Math.random();
+      }, 200);
+    },
+    onWSOpen() {
+      this.$nextTick(() => {
+        this.sendMessage();
+      });
+    },
+    sendMessage() {
+      if (!this.isWSOpen) {
+        return false;
+      }
+      const { pageCurr, pageSize, conditions, columns } = this;
+      let params = {};
+      let col, index;
+      Object.keys(conditions).forEach((v) => {
+        switch (v) {
+          case "startTimeEx":
+            if (conditions[v]) {
+              if (conditions[v][0]) {
+                params["startTime"] =
+                  moment(conditions[v][0]).format("YYYY-MM-DD") + " 00:00:00";
+              }
+              if (conditions[v][1]) {
+                params["endTime"] =
+                  moment(conditions[v][1]).format("YYYY-MM-DD") + " 23:59:59";
+              }
+            }
+            break;
+          case "status":
+            if ("" !== conditions[v]) {
+              params["confirmStatus"] = conditions[v];
+            }
+            break;
+          case "recorder":
+            if ("" !== conditions[v]) {
+              params["senderId"] = conditions[v];
+            }
+            break;
+          default:
+            params[v] = conditions[v];
+            break;
+        }
+      });
+      let data = {
+        pageSize,
+        pageCurr,
+        ...params,
+      };
+      // console.log("=====9=", data, JSON.stringify(data));
+      this.SOCKET.send(JSON.stringify(data));
+    },
+    onWSMessage(res) {
+      res = JSON.parse(res.data);
+      // // console.log(res, "=====111data");
+      this.searchData(res);
+    },
+    activeFN() {
+      this.resize();
+    },
+    uploadDefective() {
+      this.bomUploadShow = true;
+    },
+    disposeCancel() {
+      this.disposeVisible = false;
+    },
+    disposeOk() {
+      this.disposeVisible = false;
+    },
+    bomUploadCancel() {
+      this.bomUploadShow = false;
+    },
+    bomUploadOk() {
+      this.bomUploadShow = false;
+      // 璋冪敤sendMessage 鏇存柊鍒楄〃
+      this.pageCurr = 1;
+      this.sendMessage();
+    },
+    viewLog(obj) {
+      // console.log(obj);
+      this.logList = obj.hisList;
+      this.logVisible = true;
+    },
+    logCancel() {
+      this.logVisible = false;
+    },
+  },
+  watch: {
+    update(n) {
+      if (-1 != n && !this._inactive) {
+        this.$nextTick(() => {
+          const table = this.$refs.table;
+          const header = document.querySelectorAll(
+            ".doc-center-table .ant-table-header"
+          )[0].clientHeight;
+          const bar = document.querySelectorAll(".header-bar")[0].clientHeight;
+          if (table.fullScreen) {
+            this.y = table.$el.clientHeight - bar - header - 64;
+          } else {
+            const wraper = this.$refs.wraper.clientHeight;
+            const card = document.querySelectorAll(".ant-card-body")[0];
+            const { paddingBottom, paddingTop } = getComputedStyle(card, null);
+            const h =
+              wraper -
+              header -
+              64 -
+              bar -
+              parseInt(paddingBottom) -
+              parseInt(paddingTop);
+            // console.log(h, "h",wraper, header, bar );
+            this.y = h;
+          }
+        });
+      }
+    },
+    affixed() {
+      setTimeout(() => {
+        this.update = Math.random();
+      }, 200);
+    },
+  },
+  computed: {
+    ...mapGetters("account", [
+      "roles",
+      "projectManagerList",
+      "generalManagerList",
+      "permits",
+      "user",
+    ]),
+    ...mapGetters("setting", ["affixed"]),
+    canUploadBom() {
+      return checkPermit(PERMITS.uploadBom, this.permits);
+    },
+    canDownloadBom() {
+      return checkPermit(PERMITS.downloadBom, this.permits);
+    },
+    canFeedback() {
+      return checkPermit(PERMITS.feedback, this.permits);
+    },
+    abLabel() {
+      let num = this.abTbl.dataSource.length;
+      num = num > 99 ? "99+" : num;
+      return "寮傚父(" + num + ")";
+    },
+    errorLabel() {
+      let num = this.errorTbl.dataSource.length;
+      num = num > 99 ? "99+" : num;
+      return "閿欒(" + num + ")";
+    },
+    abState() {
+      let result = false;
+      if (this.abTbl.dataSource.length != 0) {
+        if (this.abTbl.ignore) {
+          result = false;
+        } else {
+          result = true;
+        }
+      } else {
+        result = false;
+      }
+
+      return result;
+    },
+    submitState() {
+      let result = {
+        code: 0,
+        msg: "瀛樺湪寮傚父鎴栭敊璇殑鏁版嵁锛岃淇鍚庡啀鎻愪氦",
+      };
+      if (this.abState) {
+        result.code = 1;
+      } else {
+        result.code = 0;
+      }
+
+      if (this.errorTbl.dataSource.length != 0) {
+        result.code = 1;
+      }
+
+      return result;
+    },
+    isTester() {
+      return this.tester.some((v) => v == this.user.name);
+    },
+  },
+  mounted() {
+    this.sendMessage();
+    window.addEventListener("resize", this.resize);
+  },
+  destroyed() {
+    window.removeEventListener("resize", this.resize);
+  },
+};
+</script>
+
+<style scoped lang="less">
+.main {
+  height: 100%;
+  position: relative;
+
+  .inner {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+  }
+}
+
+.img-wraper {
+  width: 80px;
+  height: 50px;
+  display: inline-block;
+
+  .image-view {
+    width: 100%;
+    height: 100%;
+
+    /deep/img {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+  }
+}
+
+/deep/table {
+  table-layout: fixed;
+}
+
+.text-right {
+  text-align: right;
+
+  span::after {
+    content: ":";
+    position: relative;
+    top: -0.5px;
+    margin: 0 8px 0 2px;
+  }
+}
+
+.upload {
+  padding: 10px 0;
+}
+
+.modal-footer {
+  text-align: right;
+
+  button + button {
+    margin-left: 8px;
+  }
+}
+
+.mt8 {
+  margin-top: 8px;
+}
+
+.from {
+  width: 100%;
+}
+
+.label {
+  display: flex;
+  justify-content: flex-end;
+  padding-right: 1em;
+  align-items: center;
+}
+
+.bom-list /deep/ .ant-card-body {
+  padding: 8px 24px;
+}
+
+.log-content {
+  max-height: 400px;
+  overflow-y: auto;
+
+  .user {
+    color: #23aaf2;
+    font-weight: 700;
+  }
+
+  .rest,
+  .time {
+    color: #f9be13;
+    font-weight: 700;
+  }
+
+  .version {
+    color: #0aedb2;
+    font-weight: 700;
+  }
+
+  .ant-timeline-item:first-of-type {
+    padding-top: 6px;
+  }
+}
+
+.table-title {
+  font-weight: 700;
+  color: #13c2c2;
+}
+
+.btn-grp .ant-btn {
+  min-width: 6.4em;
+}
+/deep/.level-1 > td {
+  background: #fec54b;
+}
+/deep/.level-1.level-1.level-1.ant-table-row-hover > td,
+/deep/.level-1.level-1.level-1:hover > td {
+  background: #fcd583;
+}
+/deep/.level-0 > td {
+  background: #fca4b3;
+}
+/deep/.level-0.level-0.level-0.ant-table-row-hover > td,
+/deep/.level-0.level-0.level-0:hover > td {
+  background: #f6c4cc;
+}
+/deep/.level-2 > td {
+  background: #7bfd7b;
+}
+/deep/.level-2.level-2.level-2.ant-table-row-hover > td,
+/deep/.level-2.level-2.level-2:hover > td {
+  background: #b2fbbd;
+}
+.others {
+  display: flex;
+  align-items: flex-start;
+  .label {
+    margin-right: 0.4em;
+    &::after {
+      content: ":";
+    }
+  }
+  .content {
+    flex: 1;
+    .item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 0.4em;
+      .file-name {
+        flex: 1;
+        text-align: right;
+        padding-right: 0.6em;
+      }
+      .btn {
+        & + .btn {
+          margin-left: 0.4em;
+        }
+      }
+    }
+  }
+}
+</style>
+
+<style lang="less">
+.full-modal {
+  height: 100%;
+  .ant-modal {
+    max-width: 100%;
+    height: 100%;
+    top: 0;
+    padding-bottom: 0;
+    margin: 0;
+  }
+  .ant-modal-content {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    overflow: hidden;
+    // height: calc(100vh);
+  }
+  .ant-modal-body {
+    flex: 1;
+  }
+}
+</style>
diff --git a/src/pages/resourceManage/defective/prodUpload.vue b/src/pages/resourceManage/defective/prodUpload.vue
new file mode 100644
index 0000000..c42fab9
--- /dev/null
+++ b/src/pages/resourceManage/defective/prodUpload.vue
@@ -0,0 +1,417 @@
+<template>
+  <div class="">
+    <a-form-model
+      ref="formRef"
+      name="advanced_search"
+      class="ant-advanced-search-form"
+      :model="info"
+      :rules="rules"
+    >
+      <a-row>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="鍏ュ簱鏃堕棿"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="startTime"
+          >
+            <a-date-picker
+              format="YYYY-MM-DD"
+              valueFormat="YYYY-MM-DD"
+              :allowClear="false"
+              :disabled-date="disabledDate"
+              :show-time="{ defaultValue: moment('00:00:00', 'HH:mm:ss') }"
+              v-model="info.startTime"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="鍘傚晢鍚嶇О"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="provideName"
+          >
+            <a-input
+              placeholder="璇疯緭鍏ュ巶鍟嗗悕绉�"
+              v-model.trim="info.provideName"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="浜у搧鍚嶇О"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="productName"
+          >
+            <a-input
+              placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+              v-model.trim="info.productName"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="浜у搧鍨嬪彿"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="type"
+          >
+            <a-input placeholder="璇疯緭鍏ヤ骇鍝佸瀷鍙�" v-model.trim="info.type" />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="鎬绘暟"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="sumProduct"
+          >
+            <a-input-number
+              placeholder="璇疯緭鍏ユ潵鏂欐�绘暟"
+              :min="1"
+              v-model.number="info.sumProduct"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="涓嶈壇浜у搧鏁�"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="badProduct"
+          >
+            <a-input-number
+              placeholder="璇疯緭鍏ヤ笉鑹骇鍝佹暟"
+              :min="1"
+              :max="info.sumProduct"
+              v-model.number="info.badProduct"
+            />
+          </a-form-model-item>
+        </a-col>
+        <a-col :span="24">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="涓嶈壇鐜拌薄鎻忚堪"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="content"
+          >
+            <a-textarea
+              placeholder="璇疯緭鍏ヤ笉鑹幇璞℃弿杩�"
+              v-model.trim="info.content"
+              :rows="2"
+            />
+          </a-form-model-item>
+        </a-col>
+      </a-row>
+      <a-row type="flex" class="row">
+        <a-col :span="12">
+          <a-form-model-item
+            class="ant-row-flex"
+            label="閭欢閫氱煡"
+            :labelCol="{ flex: '8em' }"
+            :wrapperCol="{ flex: 1 }"
+            prop="selectedItems"
+          >
+            <a-select
+              mode="multiple"
+              placeholder=""
+              :value="info.selectedItems"
+              style="width: 100%"
+              allow-clear
+              @change="handleChange"
+            >
+              <a-select-option
+                v-for="item in filteredOptions"
+                :key="item.name"
+                :value="item.name"
+                :disabled="!item.isHasMail"
+              >
+                {{ item.name }}
+              </a-select-option>
+            </a-select>
+          </a-form-model-item>
+        </a-col>
+      </a-row>
+      <a-row type="flex" class="row">
+        <a-col flex="8em" class="label">闄勪欢</a-col>
+        <a-col :flex="1">
+          <a-upload
+            class="upload"
+            :before-upload="beforeUpload"
+            @change="uploadChange"
+            multiple
+          >
+            <!-- accept=".zip,.rar" -->
+            <a-button type="primary">閫夋嫨闄勪欢</a-button>
+          </a-upload>
+        </a-col>
+      </a-row>
+    </a-form-model>
+    <div class="modal-footer">
+      <a-button type="danger" @click="cancel"> 鍙栨秷 </a-button>
+      <a-button type="primary" @click="ok"> 鎻愪氦 </a-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import moment from "moment";
+import { addDefective } from "./apis";
+
+import { searchDefaultMailUser } from "../../components/emailCard/apis";
+import { getUserList } from "../../permission/apis";
+
+export default {
+  name: "",
+  props: {},
+  data() {
+    const validatebadProduct = (rule, value, callback) => {
+      if (this.info.sumProduct == "") {
+        callback(new Error('璇峰厛杈撳叆鎬绘暟'));
+        this.$refs.formRef.validateField("sumProduct");
+      }
+      callback();
+    };
+    return {
+      userList: [],
+      defaultMailUsers: [],
+      selectedItems: [],
+      userListAll: [],
+      info: {
+        provideName: "",
+        productName: "",
+        content: "",
+        type: "",
+        sumProduct: "",
+        badProduct: "",
+        startTime: moment().format("YYYY-MM-DD"),
+        selectedItems: [],
+      },
+      file: null,
+      receiverIds: "",
+      receiverNames: "",
+      rules: {
+        startTime: [
+          {
+            required: true,
+            message: "璇烽�夋嫨鍏ュ簱鏃ユ湡",
+            trigger: ["blur", "change"],
+          },
+        ],
+        provideName: [
+          {
+            required: true,
+            message: "璇疯緭鍏ュ巶鍟嗗悕绉�",
+            trigger: "blur",
+          },
+        ],
+        productName: [
+          {
+            required: true,
+            message: "璇疯緭鍏ヤ骇鍝佸悕绉�",
+            trigger: "blur",
+          },
+        ],
+        type: [
+          {
+            required: true,
+            message: "璇疯緭鍏ュ瀷鍙�",
+            trigger: "blur",
+          },
+        ],
+        sumProduct: [
+          {
+            required: true,
+            message: "璇疯緭鍏ユ�绘暟",
+            trigger: "blur",
+          },
+        ],
+        badProduct: [
+          {
+            required: true,
+            message: "璇疯緭鍏ヤ笉鑹骇鍝佹暟",
+            trigger: "blur",
+          },
+          { validator: validatebadProduct, trigger: "change" },
+        ],
+        content: [
+          {
+            required: true,
+            message: "璇疯緭鍏ヤ笉鑹幇璞℃弿杩�",
+            trigger: "blur",
+          },
+        ],
+        selectedItems: [
+          {
+            required: true,
+            message: "璇峰厛鎷╄閭欢閫氱煡鐨勪汉鍛�",
+            trigger: "blur",
+          },
+          // { validator: validateSelectedItems, trigger: "change" },
+        ],
+      },
+    };
+  },
+  computed: {
+    filteredOptions() {
+      let users = this.userListAll;
+      return users
+        .filter((o) => !this.info.selectedItems.includes(o.name))
+        .map((o) => {
+          o.isHasMail = o.mail ? true : false;
+          return o;
+        });
+    },
+  },
+  methods: {
+    moment,
+    beforeUpload() {
+      return false;
+    },
+    uploadChange(data) {
+      const { file, fileList } = data;
+      if (fileList.length) {
+        this.file = fileList.map((v) => v.originFileObj);
+      } else {
+        this.file = null;
+      }
+    },
+    searchAllUserList() {
+      getUserList()
+        .then((res) => {
+          let rs = res.data;
+          if (rs.code && rs.data) {
+            this.userListAll = rs.data2;
+            this.searchDefaultMailUser();
+          }
+        })
+        .catch((error) => {
+          console.log(error);
+        });
+    },
+    searchDefaultMailUser() {
+      //  type涓�4
+      let type = 4;
+      searchDefaultMailUser(type).then((res) => {
+        let rs = res.data;
+        let data = [];
+        if (rs.code === 1) {
+          data = rs.data.map((item) => {
+            return item.user;
+          });
+        }
+        this.info.selectedItems = this.userListAll
+          .filter((o) => data.includes(o.name) && o.mail)
+          .map((item) => {
+            return item.name;
+          });
+        this.handleChange(this.info.selectedItems);
+      });
+    },
+    handleChange(selectedItems) {
+      this.info.selectedItems = selectedItems;
+      let users = this.userListAll;
+      let mailList = users.filter((o) =>
+        this.info.selectedItems.includes(o.name)
+      );
+      this.receiverNames = mailList.map((v) => v.name).join(",");
+      this.receiverIds = mailList.map((v) => v.id).join(",");
+    },
+    cancel() {
+      this.$emit("cancel");
+    },
+    ok() {
+      this.$refs["formRef"].validate((valid) => {
+        if (valid) {
+          let loading = this.$layer.loading();
+          const formData = new FormData();
+          if (this.file) {
+            this.file.forEach((v) => {
+              formData.append("multipartFileList", v);
+            });
+          }
+          let {
+            provideName,
+            content,
+            type,
+            sumProduct,
+            productName,
+            badProduct,
+            startTime,
+          } = this.info;
+          formData.append(
+            "defectiveJson",
+            JSON.stringify({
+              provideName,
+              productName,
+              content,
+              type,
+              sumProduct,
+              badProduct,
+              startTime,
+              receiverIds: this.receiverIds,
+              receiverNames: this.receiverNames,
+            })
+          );
+
+          addDefective(formData).then((res) => {
+            // console.log("res", res, "=============");
+            res = res.data;
+            this.$layer.close(loading);
+            if (res.code && res.data) {
+              this.$message.success("鎿嶄綔鎴愬姛");
+              this.$emit("ok");
+            } else {
+              this.$message.error("鎿嶄綔澶辫触");
+            }
+          });
+        } else {
+          this.$message.error("瀛樺湪鏈�氳繃妫�楠岀殑琛ㄥ崟椤�");
+          return false;
+        }
+      });
+    },
+    disabledDate(current) {
+      // Can not select days before today and today
+      return current > moment().endOf("day");
+    },
+  },
+  mounted() {
+    this.searchAllUserList();
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.modal-footer {
+  text-align: right;
+  button + button {
+    margin-left: 8px;
+  }
+}
+.row {
+  color: rgba(0, 0, 0, 0.85);
+  line-height: 30px;
+  .label {
+    text-align: right;
+  }
+  .label::after {
+    content: ":";
+    position: relative;
+    top: -0.5px;
+    margin: 0 8px 0 2px;
+  }
+}
+/deep/ .ant-input-number {
+  width: 100%;
+}
+</style>
diff --git a/src/router/config.js b/src/router/config.js
index 424bb41..a25e0d7 100644
--- a/src/router/config.js
+++ b/src/router/config.js
@@ -169,6 +169,11 @@
               },
               component: () => import('@/pages/resourceManage/specification/history'),
             },
+            {
+              path: 'defective',
+              name: '涓嶈壇鍝佺鐞�',
+              component: () => import('@/pages/resourceManage/defective'),
+            },
           ]
         },
         {

--
Gitblit v1.9.1