研发图纸文件管理系统-前端项目
he wei
2024-07-25 66b41779cf4090ed86509d9ed4b3522ded8e9049
UA 不良品页面提交
3个文件已修改
6个文件已添加
1999 ■■■■■ 已修改文件
src/components/datetimeRange/datetimeRange.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/SearchArea.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/components/filesList.vue 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/defective/apis.js 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/defective/disposeUpload.vue 389 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/defective/index.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/defective/list.vue 932 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/defective/prodUpload.vue 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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) {
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}}:
src/pages/components/filesList.vue
New file
@@ -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>
src/pages/resourceManage/defective/apis.js
New file
@@ -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
    }
  })
}
src/pages/resourceManage/defective/disposeUpload.vue
New file
@@ -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>
src/pages/resourceManage/defective/index.js
New file
@@ -0,0 +1,2 @@
import list from './list';
export default list;
src/pages/resourceManage/defective/list.vue
New file
@@ -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: "请确认是否进行归档操作?",
        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>
src/pages/resourceManage/defective/prodUpload.vue
New file
@@ -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>
src/router/config.js
@@ -169,6 +169,11 @@
              },
              component: () => import('@/pages/resourceManage/specification/history'),
            },
            {
              path: 'defective',
              name: '不良品管理',
              component: () => import('@/pages/resourceManage/defective'),
            },
          ]
        },
        {