研发图纸文件管理系统-前端项目
he wei
2023-02-25 134ba98735915c2fab5a6b4f800691c284514ebe
UA ecr变更记录
3个文件已添加
3个文件已修改
1198 ■■■■ 已修改文件
src/components/datetimeRange/datetimeRange.vue 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/ecr/apis.js 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/ecr/index.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/ecr/list.vue 623 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/product/list.vue 407 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/datetimeRange/datetimeRange.vue
@@ -1,33 +1,16 @@
<template>
  <div>
    <a-date-picker
      :getCalendarContainer="getCalendarContainer"
      v-model="startValue"
      :disabled-date="disabledStartDate"
      show-time
      format="YYYY-MM-DD HH:mm:ss"
      placeholder="Start"
      :open="startOpen"
      size="small"
      @openChange="handleStartOpenChange"
      @change="change"
    />
    <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"
      size="small" @openChange="handleStartOpenChange" @change="change(0)" />
    -
    <a-date-picker
      :getCalendarContainer="getCalendarContainer"
      v-model="endValue"
      :disabled-date="disabledEndDate"
      show-time
      format="YYYY-MM-DD HH:mm:ss"
      placeholder="End"
      :open="endOpen"
      size="small"
      @change="change"
      @openChange="handleEndOpenChange"
    />
    <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"
      size="small" @change="change(1)" @openChange="handleEndOpenChange" />
  </div>
</template>
<script>
import moment from 'moment'
export default {
  data() {
    return {
@@ -59,6 +42,13 @@
    },
  },
  methods: {
    range(start, end) {
      const result = [];
      for (let i = start; i < end; i++) {
        result.push(i);
      }
      return result;
    },
    disabledStartDate(startValue) {
      const endValue = this.endValue;
      if (!startValue || !endValue) {
@@ -71,7 +61,46 @@
      if (!endValue || !startValue) {
        return false;
      }
      // console.log(startValue.valueOf(), endValue.valueOf(), 'start, end');
      return startValue.valueOf() >= endValue.valueOf();
    },
    disabledStartTime(startValue) {
      let endValue = this.endValue;
      if (!endValue || !startValue) {
        return {
          disabledHours: () => [],
          disabledMinutes: () => [],
          disabledSeconds: () => []
        }
      }
      let startDate = startValue.format('YYYY-MM-DD');
      let startH = startValue.hour(),
        startM = startValue.minute(),
        startS = startValue.second();
      return {
        disabledHours: () => this.range(0, 24).filter(v => moment(`${startDate} ${v}:${startM}:${startS}`).valueOf() > endValue.valueOf()),
        disabledMinutes: () => this.range(0, 60).filter(v => moment(`${startDate} ${startH}:${v}:${startS}`).valueOf() > endValue.valueOf()),
        disabledSeconds: () => this.range(0, 60).filter(v => moment(`${startDate} ${startH}:${startM}:${v}`).valueOf() > endValue.valueOf()),
      };
    },
    disabledEndTime(endValue) {
      let startValue = this.startValue;
      if (!endValue || !startValue) {
        return {
          disabledHours: () => [],
          disabledMinutes: () => [],
          disabledSeconds: () => []
        }
      }
      let endDate = endValue.format('YYYY-MM-DD');
      let endH = endValue.hour(),
        endM = endValue.minute(),
        endS = endValue.second();
      return {
        disabledHours: () => this.range(0, 24).filter(v => moment(`${endDate} ${v}:${endM}:${endS}`).valueOf() < startValue.valueOf()),
        disabledMinutes: () => this.range(0, 60).filter(v => moment(`${endDate} ${endH}:${v}:${endS}`).valueOf() < startValue.valueOf()),
        disabledSeconds: () => this.range(0, 60).filter(v => moment(`${endDate} ${endH}:${endM}:${v}`).valueOf() < startValue.valueOf()),
      };
    },
    handleStartOpenChange(open) {
      this.startOpen = open;
@@ -85,7 +114,20 @@
      //   this.startOpen = true;
      // }
    },
    change() {
    change(isEnd) {
      // if (!this.startValue || !this.endValue) {
      //   return false;
      // }
      if (isEnd) {
        // 如果结束时间比开始时间小
        if (this.startValue && this.endValue.valueOf() < this.startValue.valueOf()) {
          this.endValue = this.startValue;
        }
      } else {
        if (this.endValue && this.startValue.valueOf() > this.endValue.valueOf()) {
          this.startValue = this.endValue;
        }
      }
      this.$emit("change", this.resultData);
    },
  },
src/pages/resourceManage/ecr/apis.js
New file
@@ -0,0 +1,69 @@
import axios from "@/assets/axios";
/**
 * 分页查询ECR记录
 * params: {
  createTime,
  createTime1,
  number,
  pageCurr,
  pageSize,
  subCode,
  subModel
}
 * @returns
 */
export const getList = (params) => {
  return axios({
    method: "GET",
    url: "ecr/searchEcr",
    params
  })
}
/**
 * 导入记录
 * @param {*} multipartFile
 * @returns
 */
export const ecrImportByExcel = (data) => {
  return axios({
    method: "POST",
    url: "ecr/ecrImportByExcel",
    headers: {
      "Content-Type": "multipart/form-data"
    },
    data
  })
}
/**
 * 手动录入记录
 * @param {*} {
    "changeDescription": "",
    "number": "",
    "parentModel": "",
    "proposeDate": "",
    "proposer": "",
    "solution": "",
    "subCode": "",
    "subModel": ""
}
 * @returns
 */
export const ecrImport = (data) => {
  return axios({
    method: "POST",
    url: "ecr/ecrImport",
    data
  })
}
/**
 * 导出所有记录
 * @returns
 */
export const exportExcel = () => {
  return axios({
    method: "GET",
    url: "ecr/exportExcel",
    responseType: 'blob'
  })
}
src/pages/resourceManage/ecr/index.js
New file
@@ -0,0 +1,2 @@
import List from './list.vue';
export default List;
src/pages/resourceManage/ecr/list.vue
New file
@@ -0,0 +1,623 @@
<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,
            }">
            <template slot="dataIndex" slot-scope="{ index }">
              {{ index + 1 }}
            </template>
            <template slot="title">
              <a-space class="operator">
                <span class="title">BOM变更记录</span>
                <a-popover title="" trigger="hover">
                  <a-space direction="vertical" slot="content">
                    <a-button type="primary" @click="uploadFile">文件导入</a-button>
                    <a-button type="primary" @click="customForm">手动录入</a-button>
                    <a-button type="primary" @click="exportExcel">导出全部</a-button>
                  </a-space>
                  <a-button>更多操作</a-button>
                </a-popover>
              </a-space>
            </template>
            <template slot="isNormal" slot-scope="{ record }">
              {{ record.customCode == "" ? "是" : "否" }}
            </template>
            <template slot="action" slot-scope="{ record }">
              <a href="javascript:;" v-if="!!record.changeDescription" @click="viewChangeDescription(record)">变理原因</a>
            </template>
          </advance-table>
        </a-card>
      </a-spin>
    </div>
    <!-- 变更原因 -->
    <a-modal :visible="changeDescVisible" :width="760" title="变更原因" :destroyOnClose="true" :maskClosable="false"
      @cancel="changeDescVisible = false">
      <div class="footer" slot="footer">
        <a-button @click="changeDescVisible = false" type="primary">关闭</a-button>
      </div>
      <div class="change-desc-content">{{ this.changeDesc }}</div>
    </a-modal>
    <!-- 上传ecr记录文件 -->
    <a-modal :visible="uploadVisible" :width="760" title="上传变更单" :destroyOnClose="true" :maskClosable="false" ok-text="上传"
      @cancel="bomUploadCancel" @ok="bomUploadOk">
      <a-row class="upload" type="flex">
        <a-col flex="7em"></a-col>
        <a-col flex="1">
          <a-upload :before-upload="beforeUpload" @change="uploadChange" accept=".xls,.xlsx">
            <a-button type="primary">选择变更单</a-button>
          </a-upload>
        </a-col>
      </a-row>
    </a-modal>
    <!-- 手动录入记录文件 -->
    <a-modal :visible="importVisible" :width="760" title="录入变更单" :destroyOnClose="true" :maskClosable="false" ok-text="提交"
      @cancel="importCancel" @ok="importOk">
      <a-form-model ref="formRef" name="advanced_search" class="ant-advanced-search-form" :model="info" :rules="rules">
        <a-row>
          <a-col :span="24">
            <a-form-model-item label="流水号" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }" prop="number">
              <a-input v-model="info.number" placeholder="请输入流水号"></a-input>
            </a-form-model-item>
          </a-col>
          <a-col :span="24">
            <a-form-model-item label="申请人" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }" prop="proposer">
              <a-select class="from" show-search v-model="info.proposer" placeholder="请选择申请人">
                <a-select-option v-for="(item, key) in userList" :key="'user_' + key" :value="item.name"
                  :title="item.name">
                  {{ item.name }}
                </a-select-option>
              </a-select>
            </a-form-model-item>
          </a-col>
          <a-col :span="24">
            <a-form-model-item label="申请日期" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }"
              prop="proposeDate">
              <a-date-picker v-model="info.proposeDate" format="YYYY-MM-DD" placeholder="请选择申请日期" />
            </a-form-model-item>
          </a-col>
          <a-col :span="24">
            <a-form-model-item label="变更产品" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }" prop="prod">
              <a-select class="from" show-search allowClear mode="multiple" :filterOption="filterOption"
                v-model="info.prod" @change="prodChange" placeholder="请选择产品">
                <a-select-option v-for="(item, key) in prodList" :key="'prod_' + key"
                  :value="item.parentCode + '&&' + item.parentModel">
                  ({{ item.parentCode }}) {{ item.parentName }}
                </a-select-option>
              </a-select>
            </a-form-model-item>
          </a-col>
          <a-col :span="24">
            <a-form-model-item label="所属产品型号" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }"
              prop="parentModel">
              <a-input v-model="info.parentModel" placeholder="请输入所属产品型号"></a-input>
            </a-form-model-item>
          </a-col>
          <a-col :span="24">
            <a-form-model-item label="变更原因" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }"
              prop="changeDescription">
              <a-textarea placeholder="请输入变更原因" v-model="info.changeDescription" :rows="4" />
            </a-form-model-item>
          </a-col>
          <a-col :span="24">
            <a-form-model-item label="处理方式" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }" prop="solution">
              <a-input v-model="info.solution" placeholder="请输入处理方式"></a-input>
            </a-form-model-item>
          </a-col>
        </a-row>
      </a-form-model>
    </a-modal>
  </div>
</template>
<script>
import AdvanceTable from "@/components/table/advance/AdvanceTable";
import moment from 'moment';
import { mapGetters } from "vuex";
import { getUserByRoleIds } from "@/pages/user/apis";
import { getCompareProduct } from "../product/apis";
import {
  getList,
  ecrImportByExcel,
  ecrImport,
  exportExcel
} from './apis';
export default {
  name: '',
  data() {
    return {
      info: {
        number: '',
        proposeDate: '',
        proposer: '',
        prod: [],
        parentModel: '',
        subCode: '',
        subModel: '',
        solution: '',
        changeDescription: ''
      },
      userList: [],
      prodList: [],
      rules: {
        number: [
          {
            required: true,
            message: "请输入流水号",
            trigger: "blur",
          },
        ],
        proposeDate: [
          {
            required: true,
            message: "请选择申请日期",
            trigger: ['blur', 'change'],
          },
        ],
        proposer: [
          {
            required: true,
            message: "请择申请人",
            trigger: 'blur',
          },
        ],
        prod: [
          {
            required: true,
            message: "请选择产品",
            trigger: ['blur', 'change'],
          },
        ],
        parentModel: [
          {
            required: true,
            message: "请输入所属产品型号",
            trigger: "blur",
          },
        ],
        solution: [
          {
            required: true,
            message: "请输入处理方式",
            trigger: "blur",
          },
        ],
        changeDescription: [
          {
            required: true,
            message: "请输入变更原因",
            trigger: "blur",
          },
        ],
      },
      spinning: false,
      loading: false,
      changeDesc: '',
      importVisible: false,
      changeDescVisible: false,
      uploadVisible: false,
      pageCurr: 1,
      pageSize: 10,
      total: 0,
      y: 400,
      update: -1,
      conditions: {},
      columns: [
        {
          fixed: "left",
          title: "序号",
          dataIndex: "dataIndex",
          key: "dataIndex",
          align: "center",
          width: 60,
          noSearch: true,
          scopedSlots: { customRender: "dataIndex" },
        },
        {
          title: "流水号",
          dataIndex: "number",
          key: "number",
          width: 130,
          align: "center",
          searchAble: true,
        },
        {
          title: "创建时间",
          dataIndex: "createTime",
          key: "createTime",
          align: "center",
          searchAble: true,
          dataType: 'datetimeRange',
          width: 160,
        },
        // {
        //   title: "变理原因",
        //   dataIndex: "changeDescription",
        //   key: "changeDescription",
        //   align: "center",
        //   width: 160,
        //   noSearch: true,
        // },
        {
          title: "变更料号",
          dataIndex: "subCode",
          // dataType: "boolean",
          align: "center",
          searchAble: true,
          width: 130,
        },
        {
          title: "变更型号",
          dataIndex: "subModel",
          align: "center",
          width: 180,
          searchAble: true,
        },
        {
          title: "所属产品型号",
          dataIndex: "parentModel",
          align: "center",
          width: 180,
          searchAble: true,
        },
        {
          title: "处理方式",
          dataIndex: "solution",
          key: "solution",
          align: "center",
          noSearch: true,
          width: 160,
        },
        {
          title: "申请日期",
          dataIndex: "proposeDate",
          key: "proposeDate",
          align: "center",
          width: 180,
          noSearch: true,
        },
        {
          title: "申请人",
          dataIndex: "proposer",
          key: "proposer",
          align: "center",
          width: 180,
          noSearch: true,
        },
        {
          title: "操作",
          dataIndex: "operation",
          key: "operation",
          align: "center",
          width: 228,
          fixed: "right",
          scopedSlots: { customRender: "action" },
          noSearch: true,
        },
      ],
      dataSource: [],
    }
  },
  components: {
    AdvanceTable
  },
  computed: {
    ...mapGetters("setting", ["affixed"]),
  },
  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);
    },
  },
  methods: {
    onSearch(conditions, searchOptions) {
      // console.log(conditions, '=======');
      // console.log(searchOptions);
      this.pageCurr = 1;
      this.conditions = conditions;
      this.getList();
    },
    onPageChange(page, pageSize) {
      this.pageCurr = page;
      this.pageSize = pageSize;
      this.getList();
    },
    onSizeChange(current, size) {
      this.pageCurr = 1;
      this.pageSize = size;
      this.getList();
    },
    onRefresh(conditions) {
      this.conditions = conditions;
      this.getList();
    },
    onReset(conditions) {
      this.conditions = conditions;
      this.getList();
    },
    getList() {
      const { pageCurr, pageSize, conditions, columns } = this;
      let params = {};
      Object.keys(conditions).forEach((v) => {
        switch (v) {
          case "createTime":
            if (conditions[v]) {
              params["createTime"] = conditions[v][0] ? conditions[v][0].format('YYYY-MM-DD HH:mm:ss') : '1982-01-01 00:00:00';
              params["createTime1"] = conditions[v][1] ? conditions[v][1].format('YYYY-MM-DD HH:mm:ss') : moment().format('YYYY-MM-DD HH:mm:ss');
            }
            break;
          default:
            params[v] = conditions[v];
            break;
        }
      });
      let _params = {
        createTime: '1982-01-01 00:00:00',
        createTime1: moment().format('YYYY-MM-DD HH:mm:ss'),
        pageSize,
        pageCurr,
        ...params,
      };
      getList(_params).then(res => {
        let { code, data, data2 } = res.data;
        let list = [];
        let total = 0;
        if (code && data) {
          list = data2.list;
          total = data2.total;
        }
        if (-1 == this.update) {
          this.update = Math.random();
        }
        this.dataSource = list;
        this.total = total;
      });
    },
    beforeUpload() {
      return false;
    },
    uploadChange(data) {
      const { file, fileList } = data;
      if (fileList.length > 1) {
        fileList.shift();
        // console.log(file, fileList, "90909090");
      }
      if (fileList.length) {
        // this.file = fileList[0];
        // console.log(file == fileList[0], file == fileList[0].originFileObj)
        this.file = fileList[0].originFileObj;
      } else {
        this.file = null;
      }
    },
    uploadCancel() {
      this.uploadShow = false;
    },
    uploadFile() {
      this.file = null;
      this.uploadVisible = true;
    },
    bomUploadOk() {
      if (!this.file) {
        this.$message.error('请选择要上传的文件');
        return false;
      }
      let loading = this.$layer.loading({ shade: true });
      const formData = new FormData();
      formData.append('multipartFile', this.file);
      ecrImportByExcel(formData).then(res => {
        let { code, data } = res.data;
        this.$layer.close(loading);
        if (code && data) {
          this.$message.success('导入成功');
          this.uploadVisible = false;
        } else {
          this.$message.error('导入失败');
        }
      });
    },
    bomUploadCancel() {
      this.uploadVisible = false;
    },
    customForm() {
      this.importVisible = true;
    },
    importCancel() {
      console.log(1)
    },
    importOk() {
      console.log(this.info);
      let {
        number,
        proposeDate,
        proposer,
        solution,
        subCode,
        subModel,
        parentModel,
        changeDescription
      } = this.info;
      this.$refs.formRef.validate((valid) => {
        if (!valid) {
          this.$message.error("存在未通过检验的表单项");
          return false;
        } else {
          let params = {
            number,
            proposeDate,
            proposer,
            solution,
            subCode,
            subModel,
            parentModel,
            changeDescription
          };
          this.ecrImport(params);
        }
      });
    },
    ecrImport(args) {
      let loading = this.$layer.loading({ shade: true });
      ecrImport(args).then(res => {
        let { code, data } = res.data;
        if (code && data) {
          this.$message.success('提交成功');
          this.getList();
          this.importVisible = false;
        } else {
          this.$message.error('提交失败');
        }
        this.$layer.close(loading);
      });
    },
    exportExcel() {
      exportExcel().then(res => {
        // console.log(res, '===res')
        let { headers, data, status } = res;
        if (200 == status && data) {
          let url = window.URL.createObjectURL(data);
          const matchRes = /filename=(.*)/.exec(headers["content-disposition"]);
          const fileName = matchRes ? matchRes[1].trim() : "未知文件名.xls";
          let link = document.createElement("a");
          link.style.display = "none";
          link.href = url;
          link.download = fileName;
          document.body.appendChild(link);
          link.click();
          document.body.removeChild(link);
          window.URL.revokeObjectURL(url);
        } else {
          this.$message.error("操作失败");
        }
      })
    },
    viewChangeDescription(record) {
      this.changeDesc = record.changeDescription;
      this.changeDescVisible = true;
    },
    resize() {
      setTimeout(() => {
        this.update = Math.random();
      }, 200);
    },
    activeFN() {
      this.resize();
    },
    // 获取多个角色组的所有用户
    getUserByRoleIds() {
      getUserByRoleIds("1001,1002,1003")
        .then((res) => {
          let { code, data, data2 } = res.data;
          let list = [];
          if (code && data) {
            // console.log(data2);
            list = data2;
          }
          // console.log(list, "====list");
          this.userList = list;
        })
        .catch((err) => {
          console.error(err);
        });
    },
    // 获取产品列表
    getProdList() {
      getCompareProduct()
        .then((res) => {
          let { code, data, data2 } = res.data;
          let prodList = [];
          if (code && data) {
            prodList = data2.filter(v => '' == v.customCode);
          }
          this.prodList = prodList;
        })
        .catch((err) => {
          console.error(err);
        });
    },
    filterOption(input, option) {
      return option.componentOptions.children[0].text.indexOf(input) != -1;
    },
    prodChange(value) {
      this.info.prod = value;
      this.info.subCode = value.map(v => v.split("&&")[0]).join('/');
      this.info.subModel = value.map(v => v.split("&&")[1]).join('/');
    }
  },
  mounted() {
    this.getList();
    this.getUserByRoleIds();
    this.getProdList();
  }
}
</script>
<style scoped lang="less">
.main {
  height: 100%;
  position: relative;
  .inner {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
  }
  .doc-center-table {
    min-height: 380px;
  }
}
.change-desc-content {
  max-height: 400px;
  overflow-y: auto;
}
</style>
src/pages/resourceManage/product/list.vue
@@ -3,20 +3,9 @@
    <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="{
          <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,
@@ -28,17 +17,14 @@
                `第 ${range[0]}-${range[1]} 条,总计 ${total} 条`,
              onChange: onPageChange,
              onShowSizeChange: onSizeChange,
            }"
          >
            }">
            <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="uploadBom"
                  >新增</a-button
                >
                <a-button v-if="canUploadBom" type="primary" @click="uploadBom">新增</a-button>
                <a-button type="primary" @click="prodDiff">产品比较</a-button>
              </a-space>
            </template>
@@ -55,34 +41,15 @@
              <a @click="goDetails(record)">详情</a>
              <a-divider type="vertical"></a-divider>
              <a-popover title="" trigger="hover">
                <a-space direction="vertical" slot="content">
                  <a-button
                    :disabled="record.version == -1"
                    type="primary"
                    @click="edit(record)"
                    >编辑</a-button
                  >
                  <a-button
                    v-if="canDownloadBom"
                    :disabled="record.version == -1"
                    type="primary"
                    @click="checkLock(record)"
                    >下载</a-button
                  >
                  <a-button
                    type="primary"
                    v-if="canUploadBom"
                    :disabled="record.version == -1"
                    @click="showCustom(record)"
                    >定制</a-button
                  >
                  <a-button
                    type="primary"
                    v-if="canFeedback"
                    :disabled="record.version == -1"
                    @click="showFeedback(record)"
                    >反馈</a-button
                  >
                <a-space class="btn-grp" direction="vertical" slot="content">
                  <a-button :disabled="record.version == -1" type="primary" @click="edit(record)">编辑</a-button>
                  <a-button v-if="canDownloadBom" :disabled="record.version == -1" type="primary"
                    @click="checkLock(record)">下载</a-button>
                  <a-button type="primary" v-if="canUploadBom" :disabled="record.version == -1"
                    @click="showCustom(record)">定制</a-button>
                  <a-button type="primary" v-if="canFeedback" :disabled="record.version == -1"
                    @click="showFeedback(record)">反馈</a-button>
                  <a-button type="primary" v-if="record.ecrList.length > 0" @click="showEcrlist(record)">ecr记录</a-button>
                </a-space>
                <a>更多</a>
              </a-popover>
@@ -92,15 +59,8 @@
      </a-spin>
    </div>
    <!-- 上传软件 -->
    <a-modal
      :visible="uploadShow"
      :footer="null"
      :width="500"
      title="上传软件"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="uploadCancel"
    >
    <a-modal :visible="uploadShow" :footer="null" :width="500" title="上传软件" :destroyOnClose="true" :maskClosable="false"
      @cancel="uploadCancel">
      <div class="">
        <a-row class="title">
          <a-col :span="6" class="text-right">
@@ -114,58 +74,27 @@
        </a-row>
        <a-row class="upload">
          <a-col :span="15" :offset="7">
            <a-upload
              :before-upload="beforeUpload"
              @change="uploadChange"
              accept=".zip"
            >
            <a-upload :before-upload="beforeUpload" @change="uploadChange" accept=".zip">
              <a-button type="primary">上传软件</a-button>
            </a-upload>
          </a-col>
        </a-row>
        <a-form-model
          ref="formRef"
          name="advanced_search"
          class="ant-advanced-search-form"
          :model="info"
          :rules="rules"
        >
        <a-form-model ref="formRef" name="advanced_search" class="ant-advanced-search-form" :model="info" :rules="rules">
          <a-row>
            <a-col :span="24">
              <a-form-model-item
                label="审核人"
                :labelCol="{ span: 6 }"
                :wrapperCol="{ span: 15, offset: 1 }"
                prop="nextUser"
              >
                <a-select
                  show-search
                  v-model="info.nextUser"
                  placeholder="请选择审核人"
                >
                  <a-select-option
                    v-for="(item, key) in userList"
                    :key="'key' + key"
                    :value="item.id"
                    :title="item.name"
                  >
              <a-form-model-item label="审核人" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }"
                prop="nextUser">
                <a-select show-search v-model="info.nextUser" placeholder="请选择审核人">
                  <a-select-option v-for="(item, key) in userList" :key="'key' + key" :value="item.id" :title="item.name">
                    {{ item.name }}
                  </a-select-option>
                </a-select>
              </a-form-model-item>
            </a-col>
            <a-col :span="24">
              <a-form-model-item
                label="工单描述"
                :labelCol="{ span: 6 }"
                :wrapperCol="{ span: 15, offset: 1 }"
                prop="description"
              >
                <a-textarea
                  placeholder="请输入工单描述"
                  v-model="info.description"
                  :rows="4"
                />
              <a-form-model-item label="工单描述" :labelCol="{ span: 6 }" :wrapperCol="{ span: 15, offset: 1 }"
                prop="description">
                <a-textarea placeholder="请输入工单描述" v-model="info.description" :rows="4" />
              </a-form-model-item>
            </a-col>
          </a-row>
@@ -177,35 +106,17 @@
      </div>
    </a-modal>
    <!-- 编辑产品 -->
    <a-modal
      :visible="editShow"
      :footer="null"
      :width="1200"
      title="修改产品替换件信息"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="cancel"
    >
    <a-modal :visible="editShow" :footer="null" :width="1200" title="修改产品替换件信息" :destroyOnClose="true"
      :maskClosable="false" @cancel="cancel">
      <change-parts :parent-data="editObj" @close="cancel"></change-parts>
    </a-modal>
    <!-- 上传bom -->
    <a-modal
      :visible="bomUploadShow"
      :width="760"
      title="上传产品BOM"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="bomUploadCancel"
      @ok="bomUploadOk"
    >
    <a-modal :visible="bomUploadShow" :width="760" title="上传产品BOM" :destroyOnClose="true" :maskClosable="false"
      @cancel="bomUploadCancel" @ok="bomUploadOk">
      <a-row class="upload" type="flex">
        <a-col flex="7em"></a-col>
        <a-col flex="1">
          <a-upload
            :before-upload="beforeUpload"
            @change="uploadChange"
            accept=".zip"
          >
          <a-upload :before-upload="beforeUpload" @change="uploadChange" accept=".zip">
            <a-button type="primary">上传BOM</a-button>
          </a-upload>
        </a-col>
@@ -213,20 +124,9 @@
      <a-row type="flex">
        <a-col class="label" flex="7em"><span>基于产品</span></a-col>
        <a-col flex="1">
          <a-select
            class="from"
            show-search
            allowClear
            :filterOption="prodFilter"
            v-model="fromProd"
            placeholder="请选择是从哪款产品升级来的"
          >
            <a-select-option
              v-for="(item, key) in prodList"
              :key="'prod_' + key"
              :value="item.id"
              :title="item.name"
            >
          <a-select class="from" show-search allowClear :filterOption="prodFilter" v-model="fromProd"
            placeholder="请选择是从哪款产品升级来的">
            <a-select-option v-for="(item, key) in prodList" :key="'prod_' + key" :value="item.id" :title="item.name">
              {{ item.parentName }} ({{ item.parentCode }})
              {{ item.customCode }}
            </a-select-option>
@@ -235,99 +135,41 @@
      </a-row>
    </a-modal>
    <!-- 解析Bom -->
    <a-modal
      :visible="prodUploadShow"
      :footer="null"
      :width="960"
      title="上传产品BOM"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="prodUploadCancel"
    >
    <a-modal :visible="prodUploadShow" :footer="null" :width="960" title="上传产品BOM" :destroyOnClose="true"
      :maskClosable="false" @cancel="prodUploadCancel">
      <a-tabs type="card" tabPosition="left">
        <a-tab-pane key="1" tab="清单">
          <draw-upload
            class="bom-list"
            :list="resList"
            :y="320"
            :no-footer="true"
          ></draw-upload>
          <draw-upload class="bom-list" :list="resList" :y="320" :no-footer="true"></draw-upload>
        </a-tab-pane>
        <a-tab-pane key="2" tab="差异">
          <diff-list :list="diffData"></diff-list>
        </a-tab-pane>
        <a-tab-pane key="3" :tab="abLabel">
          <a-table
            ref="aTable"
            size="small"
            :scroll="{ y: 700 }"
            bordered
            :columns="abTbl.columns"
            :data-source="abTbl.dataSource"
            :pagination="false"
            rowKey="subCode"
          ></a-table>
          <a-table ref="aTable" size="small" :scroll="{ y: 700 }" bordered :columns="abTbl.columns"
            :data-source="abTbl.dataSource" :pagination="false" rowKey="subCode"></a-table>
          <div style="text-align: right; padding: 8px">
            <a-button
              @click="abTbl.ignore = true"
              :type="abState ? 'danger' : 'primary'"
              :icon="abState ? 'question' : 'check'"
              >{{ abState ? "未确认" : "已确认" }}</a-button
            >
            <a-button @click="abTbl.ignore = true" :type="abState ? 'danger' : 'primary'"
              :icon="abState ? 'question' : 'check'">{{ abState ? "未确认" : "已确认" }}</a-button>
          </div>
        </a-tab-pane>
        <a-tab-pane key="4" :tab="errorLabel">
          <a-table
            ref="aTable"
            size="small"
            :scroll="{ y: 700 }"
            bordered
            :columns="errorTbl.columns"
            :data-source="errorTbl.dataSource"
            :pagination="false"
            rowKey="subCode"
          ></a-table>
          <a-table ref="aTable" size="small" :scroll="{ y: 700 }" bordered :columns="errorTbl.columns"
            :data-source="errorTbl.dataSource" :pagination="false" rowKey="subCode"></a-table>
        </a-tab-pane>
      </a-tabs>
      <prod-upload
        class="mt8"
        @ok="submit"
        @cancel="prodUploadCancel"
      ></prod-upload>
      <prod-upload class="mt8" @ok="submit" @cancel="prodUploadCancel"></prod-upload>
    </a-modal>
    <!-- 定制 -->
    <a-modal
      :visible="customShow"
      :footer="null"
      :width="800"
      title="产品定制"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="customCancel"
    >
      <prod-upload
        class="mt8"
        :prod-info="customProd"
        @ok="custom"
        @cancel="customCancel"
      ></prod-upload>
    <a-modal :visible="customShow" :footer="null" :width="800" title="产品定制" :destroyOnClose="true" :maskClosable="false"
      @cancel="customCancel">
      <prod-upload class="mt8" :prod-info="customProd" @ok="custom" @cancel="customCancel"></prod-upload>
    </a-modal>
    <!-- 日志 -->
    <a-modal
      :visible="logVisible"
      :footer="null"
      :width="800"
      title="操作日志"
      :destroyOnClose="true"
      @cancel="logCancel"
    >
    <a-modal :visible="logVisible" :footer="null" :width="800" title="操作日志" :destroyOnClose="true" @cancel="logCancel">
      <div class="log-content">
        <a-timeline v-if="logList.length">
          <a-timeline-item
            v-for="(item, idx) in logList"
            :key="'log_' + idx"
            :color="item.lockFlag == 1 ? 'red' : 'green'"
          >
          <a-timeline-item v-for="(item, idx) in logList" :key="'log_' + idx"
            :color="item.lockFlag == 1 ? 'red' : 'green'">
            <div>
              <span class="user">{{ item.owner }}</span> 在
              <span class="time">{{ item.createTime }}</span>
@@ -341,56 +183,43 @@
      </div>
    </a-modal>
    <!-- 锁定清单 -->
    <a-modal
      :visible="lockListVisible"
      :width="800"
      title="下载提示 (有锁定文件)"
      :destroyOnClose="true"
      @cancel="lockListCancel"
      @ok="lockListOk"
    >
    <a-modal :visible="lockListVisible" :width="800" title="下载提示 (有锁定文件)" :destroyOnClose="true" @cancel="lockListCancel"
      @ok="lockListOk">
      <!-- bom清单中存在锁定图纸的物料 -->
      <template v-if="bomLockList.length">
        <div class="table-title">bom清单中存在锁定图纸的物料</div>
        <a-table
          size="small"
          :scroll="{ y: 150 }"
          bordered
          :columns="bomLockColumns"
          :data-source="bomLockList"
          :pagination="false"
          :expandRowByClick="true"
          :row-key="(record, index) => index"
        ></a-table>
        <a-table size="small" :scroll="{ y: 150 }" bordered :columns="bomLockColumns" :data-source="bomLockList"
          :pagination="false" :expandRowByClick="true" :row-key="(record, index) => index"></a-table>
      </template>
      <!-- 其他附件中存在锁定文件 -->
      <template v-if="otherLockList.length">
        <div class="table-title">其他附件中存在锁定文件</div>
        <a-table
          size="small"
          :scroll="{ y: 150 }"
          bordered
          :columns="otherLockColumns"
          :data-source="otherLockList"
          :pagination="false"
          :expandRowByClick="true"
          :row-key="(record, index) => index"
        ></a-table>
        <a-table size="small" :scroll="{ y: 150 }" bordered :columns="otherLockColumns" :data-source="otherLockList"
          :pagination="false" :expandRowByClick="true" :row-key="(record, index) => index"></a-table>
      </template>
    </a-modal>
    <a-modal
      :visible="feedbackShow"
      :width="800"
      title="问题反馈"
      :destroyOnClose="true"
      :footer="false"
      @cancel="feedbackCancel"
    >
      <feedback-form
        :prod-data="customProd"
        @cancel="feedbackCancel"
        @ok="feedbackOk"
      ></feedback-form>
    <a-modal :visible="feedbackShow" :width="800" title="问题反馈" :destroyOnClose="true" :footer="false"
      @cancel="feedbackCancel">
      <feedback-form :prod-data="customProd" @cancel="feedbackCancel" @ok="feedbackOk"></feedback-form>
    </a-modal>
    <!-- ecr列表 -->
    <a-modal :visible="ecrListVisible" :width="800" title="ecr记录" :destroyOnClose="true" @cancel="ecrListCancel">
      <div class="footer" slot="footer">
        <a-button type="primary" @click="ecrListCancel">关闭</a-button>
      </div>
      <div class="ecr-content">
        <a-table ref="aTable" size="small" :scroll="{ y: 700 }" bordered :columns="ecrColumns" :data-source="ecrList"
          :pagination="false" rowKey="id">
          <template slot="changeDesc" slot-scope="text, record">
            <a-tooltip placement="topLeft">
              <template slot="title">
                {{ record.changeDescription }}
              </template>
              {{ record.changeDescription }}
            </a-tooltip>
          </template>
        </a-table>
      </div>
    </a-modal>
  </div>
</template>
@@ -465,12 +294,54 @@
        ellipsis: true,
      },
    ];
    const ecrColumns = [
      {
        title: "流水号",
        dataIndex: "number",
        width: 120,
        align: "center",
      },
      {
        title: "申请人",
        dataIndex: "proposer",
        align: "center",
        ellipsis: true,
      },
      {
        title: "申请日期",
        dataIndex: "proposeWebsocket",
        align: "center",
        ellipsis: true,
      },
      {
        title: "创建时间",
        dataIndex: "createTime",
        width: 120,
        align: "center",
      },
      {
        title: "处理方式",
        dataIndex: "solution",
        align: "center",
        ellipsis: true,
      },
      {
        title: "变更原因",
        dataIndex: "changeDescription",
        align: "center",
        ellipsis: true,
        scopedSlots: { customRender: 'changeDesc' }
      },
    ];
    return {
      ecrColumns,
      feedbackShow: false,
      lockListVisible: false,
      currentObj: null,
      bomLockList: [],
      otherLockList: [],
      ecrList: [],
      ecrListVisible: false,
      logVisible: false,
      logList: [],
      fromProd: undefined,
@@ -692,7 +563,12 @@
        let data = [];
        let total = 0;
        if (res.code && res.data) {
          data = res.data2.list;
          data = res.data2.list.map(v => {
            if ('' != v.customCode) {
              v.ecrList = [];
            }
            return v;
          });
          total = res.data2.total;
        }
        this.dataSource = data;
@@ -1120,6 +996,13 @@
    prodDiff() {
      this.$router.push("/resource/product-diff");
    },
    showEcrlist(record) {
      this.ecrList = record.ecrList;
      this.ecrListVisible = true;
    },
    ecrListCancel() {
      this.ecrListVisible = false;
    }
  },
  watch: {
    update(n) {
@@ -1241,6 +1124,7 @@
.main {
  height: 100%;
  position: relative;
  .inner {
    position: absolute;
    left: 0;
@@ -1249,13 +1133,16 @@
    bottom: 0;
  }
}
.img-wraper {
  width: 80px;
  height: 50px;
  display: inline-block;
  .image-view {
    width: 100%;
    height: 100%;
    /deep/img {
      width: 100%;
      height: 100%;
@@ -1263,11 +1150,14 @@
    }
  }
}
/deep/table {
  table-layout: fixed;
}
.text-right {
  text-align: right;
  span::after {
    content: ":";
    position: relative;
@@ -1275,51 +1165,68 @@
    margin: 0 8px 0 2px;
  }
}
.upload {
  padding: 10px 0;
}
.modal-footer {
  text-align: right;
  button + button {
  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;
  }
  .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: 6em;
}
</style>
src/router/config.js
@@ -103,6 +103,11 @@
              name: '软件中心',
              component: () => import('@/pages/resourceManage/software'),
            },
            {
              path: 'ecr',
              name: 'BOM变更记录',
              component: () => import('@/pages/resourceManage/ecr'),
            },
            // {
            //   path: 'flow-card',
            //   name: '流程卡',