研发图纸文件管理系统-前端项目
he wei
2025-02-26 75d229e997cba687ecbda2d7e41d42ed01bf8bfd
src/pages/resourceManage/product/details/details.vue
@@ -1,26 +1,35 @@
<template>
  <a-layout class="main">
    <a-layout-sider width="260">
      <list
        class="list"
        :list="versionList"
        @select="selectChanged"
        @diff="diff"
      ></list>
    </a-layout-sider>
    <a-layout>
  <div class="main">
    <div :class="['side-content', { hide: !sideVisible }]">
      <div class="side-inner">
        <list
          class="list"
          :list="versionList"
          @select="selectChanged"
          @diff="diff"
        ></list>
        <div
          class="handler"
          :title="sideVisible ? '隐藏' : '展开'"
          @click="toggleSide"
        >
          <a-icon class="icon" type="left" />
        </div>
      </div>
    </div>
    <a-layout class="main-content">
      <a-layout-header>
        <a-card>
          <a-descriptions title="产品信息">
            <a-descriptions-item label="母料型号">{{
        <a-card size="small">
          <a-descriptions :column="7" title="">
            <a-descriptions-item label="产品编号">{{
              parentCode
            }}</a-descriptions-item>
            <a-descriptions-item label="产品型号" :span="2">{{
              parentModel
            }}</a-descriptions-item>
            <!-- <a-descriptions-item label="母料名称">{{
              info.parentName
            <a-descriptions-item label="产品名称" :span="2">{{
              parentName
            }}</a-descriptions-item>
            <a-descriptions-item label="母料编号">{{
              info.parentCode
            }}</a-descriptions-item> -->
            <a-descriptions-item label="定制单号" :span="2">{{
              customCode
            }}</a-descriptions-item>
@@ -41,14 +50,17 @@
                  :data-source="dataSource"
                  :pagination="false"
                  :expandRowByClick="true"
                  :row-key="(record, index) => record.subCode + '_' + index"
                  :rowClassName="
                    (record) =>
                      record.children && record.children.length
                        ? 'is-replace'
                        : ''
                  :row-key="
                    (record, index) =>
                      record.p_subCode
                        ? record.p_subCode + '_' + record.subCode + '_' + index
                        : record.subCode + '_' + index
                  "
                  :rowClassName="rowClassFn"
                >
                  <template slot="dataIndex" slot-scope="text, record, index">
                    {{ index + 1 }}
                  </template>
                  <template slot="pictureUrl" slot-scope="text">
                    <div class="img-wraper">
                      <image-view v-if="text" :url="webUrl + text"></image-view>
@@ -56,10 +68,97 @@
                  </template>
                  <template slot="action" slot-scope="text, record">
                    <div v-if="record.dwgUrl">
                      <a @click="dwgReview(record.dwgUrl)">预览</a>
                      <a-divider type="vertical"></a-divider>
                      <a @click="downloadLog(record)">下载</a>
                      <a v-if="canViewDoc" @click="dwgReview(record.dwgUrl)"
                        >预览</a
                      >
                      <a-divider
                        v-if="canViewDoc && canDownloadDoc"
                        type="vertical"
                      ></a-divider>
                      <a
                        v-if="canDownloadDoc"
                        @click="showReason('downloadLog', record)"
                        >下载</a
                      >
                    </div>
                    <template
                      v-if="
                        record.softwares &&
                          record.softwares.length &&
                          canDownloadSoftware
                      "
                    >
                      <a-divider
                        v-if="record.dwgUrl"
                        type="vertical"
                      ></a-divider>
                      <a-popover title="" trigger="hover">
                        <div class="" slot="content" style="width: 450px">
                          <a-table
                            size="small"
                            :scroll="{ y: 300 }"
                            bordered
                            :columns="softwareColumns"
                            :data-source="record.softwares"
                            :pagination="false"
                            :expandRowByClick="true"
                            :row-key="(record1, index) => index"
                          >
                            <template slot="action" slot-scope="text, record1">
                              <a
                                v-if="canDownloadSoftware"
                                @click="showReason('downloadLog', record1)"
                                >下载</a
                              >
                            </template>
                          </a-table>
                        </div>
                        <a>软件列表</a>
                      </a-popover>
                    </template>
                    <template v-if="/^0120/.test(record.subCode)">
                      <a-divider
                        v-if="
                          record.softwares &&
                            record.softwares.length &&
                            canDownloadSoftware
                        "
                        type="vertical"
                      ></a-divider>
                      <a href="javascript:;" @click="toDetails0120(record)"
                        >详情</a
                      >
                    </template>
                    <template v-if="/^08|^09/.test(record.subCode)">
                      <a-divider
                        v-if="
                          record.softwares &&
                            record.softwares.length &&
                            canDownloadSoftware
                        "
                        type="vertical"
                      ></a-divider>
                      <a
                        href="javascript:;"
                        @click="toDetails_type(record, '0809')"
                        >详情</a
                      >
                    </template>
                    <template v-if="/^0235/.test(record.subCode)">
                      <a-divider
                        v-if="
                          record.softwares &&
                            record.softwares.length &&
                            canDownloadSoftware
                        "
                        type="vertical"
                      ></a-divider>
                      <a
                        href="javascript:;"
                        @click="toDetails_type(record, '0235')"
                        >详情</a
                      >
                    </template>
                  </template>
                </a-table>
              </a-spin>
@@ -73,30 +172,85 @@
      <a-layout-footer>
        <a-card>
          <template v-if="dataSource.length">
            <a-popover title="" v-if="otherDoc.length">
              <files-table slot="content" :list="otherDoc"></files-table>
            <!-- <a-popover
              title=""
              v-if="otherDoc.length && currentVersion.enabled"
            >
              <div class="file-list" slot="content">
                <files-table :list="otherDoc"></files-table>
              </div>
              <a-button type="primary">其他附件</a-button>
            </a-popover>
            <a-button type="primary" @click="zipDownload"
            </a-popover> -->
            <a-button type="primary" @click="viewLog">状态日志</a-button>
            <a-button type="primary" @click="downloadLogs">下载日志</a-button>
            <a-button
              v-if="otherDoc.length && currentVersion.enabled == 1"
              type="primary"
              @click="showOtherDoc"
              >其他附件</a-button
            >
            <a-button
              v-if="
                ((canDownloadBom && currentVersion.enabled == 1) || isTester) &&
                  !is0120
              "
              type="primary"
              @click="checkLock('zipDownload')"
              >bom下载</a-button
            >
            <a-button
              v-if="
                (((canDownloadBom || canDownloadOriginBom) &&
                  currentVersion.enabled == 1) ||
                  isTester) &&
                  originalZipUrl &&
                  !is0120
              "
              type="primary"
              v-if="softwareList.length"
              @click="checkLock('OriginalZipDownload')"
              >下载原始包</a-button
            >
            <a-button
              v-if="isTester && originalZipUrl"
              type="primary"
              @click="viewRar"
              >查看原始包</a-button
            >
            <a-button
              type="primary"
              v-if="
                softwareList.length &&
                  canDownloadSoftware &&
                  currentVersion.enabled == 1
              "
              @click="showSoftwareDownload"
              >软件下载</a-button
            >
            <a-button
              type="primary"
              v-if="!currentVersion.enabled"
              v-if="
                currentVersion.enabled == 0 &&
                  canLockBom &&
                  (!is0120 || maxVersionid == currentVersion.id)
              "
              @click="changeStatus"
              >激活版本</a-button
            >
            <a-button
              type="primary"
              v-if="currentVersion.enabled"
              v-if="currentVersion.enabled == -1 && isTester"
              @click="changeStatus"
              >解锁版本</a-button
            >
            <a-button
              type="primary"
              v-if="currentVersion.enabled == 1 && canLockBom"
              @click="changeStatus"
              >锁定版本</a-button
            >
            <a-button type="primary" @click="viewSopFile">SOP查看</a-button>
            <a-button v-if="originalZipUrl" type="primary" @click="preview"
              >原始包预览</a-button
            >
          </template>
        </a-card>
@@ -106,16 +260,16 @@
    <a-modal
      :visible="softwareDownloadShow"
      :footer="null"
      :width="600"
      :width="960"
      title="软件下载"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="softwareDownloadCancel"
    >
      <a-table
        ref="aTable"
        size="small"
        :scroll="{ y: 300 }"
        ref="softwareTable"
        bordered
        :columns="softwareColumns"
        :data-source="softwareList"
@@ -124,42 +278,344 @@
        :row-key="(record, index) => index"
      >
        <template slot="action" slot-scope="text, record">
          <a @click="downloadLog(record)">下载</a>
          <a @click="showReason('downloadLog', record)">下载</a>
        </template>
      </a-table>
      <pop
        :visible.sync="popVisible"
        :x="popPosition.x"
        :y="popPosition.y"
        :info="popInfo"
        :position="popPosition.dir"
      ></pop>
    </a-modal>
  </a-layout>
    <!-- 差异 -->
    <a-modal
      :visible="diffShow"
      :footer="null"
      :width="860"
      title="差异"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="diffCancel"
    >
      <diff-list :list="diffData"></diff-list>
    </a-modal>
    <!-- 操作原因 -->
    <a-modal
      :visible="reasonVisible"
      :width="460"
      title="操作原因"
      :destroyOnClose="true"
      :maskClosable="false"
      @cancel="reasonCancel"
      @ok="reasonOk"
    >
      <a-form-model-item ref="name" label="操作原因">
        <a-input
          type="textarea"
          v-model="reason"
          placeHolder="请输入操作原因"
        />
      </a-form-model-item>
    </a-modal>
    <!-- 其他附件列表弹窗 -->
    <a-modal
      :visible="otherDocVisible"
      title="其他附件"
      :destroyOnClose="true"
      :width="800"
      :footer="null"
      @cancel="otherDocCancel"
    >
      <files-table
        :list="otherDoc"
        :info="info"
        :type="12"
        :oprate-version="currentVersion.versionTime"
      ></files-table>
    </a-modal>
    <!-- 锁定清单 -->
    <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>
      </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>
      </template>
    </a-modal>
    <!-- 日志 -->
    <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'"
          >
            <div>
              <span class="user">{{ item.owner }}</span> 在
              <span class="time">{{ item.createTime }}</span>
              {{
                { "-1": "上传", "0": "激活", "1": "锁定" }[item.lockFlag]
              }}了版本
              <span class="version">{{ item.versionTime }}</span>
            </div>
            <div>操作原因: {{ item.reason ? item.reason : "无" }}</div>
          </a-timeline-item>
        </a-timeline>
        <a-empty v-else />
      </div>
    </a-modal>
    <download-reason
      :reason-visible.sync="downloadReasonVisible"
      v-if="downloadReasonVisible"
      @ok="downloadReasonOk"
    ></download-reason>
    <download-logs
      :visible.sync="downloadlogVisible"
      v-if="downloadlogVisible"
      :type="12"
      :oprate-info="oprateInfo"
    ></download-logs>
    <a-modal
      :visible="fileTreeVisible"
      :footer="null"
      :width="860"
      title="文件列表"
      :destroyOnClose="true"
      @cancel="fileTreeVisible = false"
    >
      <div class="log-content">
        <a-tree
          v-if="fileTree.length"
          :show-line="true"
          defaultExpandAll
          :tree-data="fileTree"
        />
        <a-empty v-else />
      </div>
    </a-modal>
    <a-modal
      :visible="fileTreeViewVisible"
      :footer="null"
      :width="860"
      title="文件列表"
      :destroyOnClose="true"
      @cancel="fileTreeViewVisible = false"
    >
      <a-input-search
        style="margin-bottom: 8px"
        v-model="searchValue"
        placeholder="输入关键词搜索文件名"
        @change="searchChanged"
      />
      <div class="log-content">
        <a-tree
          class="file-tree-view"
          v-if="fileTreeView.length && treeReset"
          :show-line="true"
          defaultExpandAll
          :tree-data="fileTreeViewRes"
        >
          <template slot="custom" slot-scope="item">
            <div class="flex-r space-between">
              <span v-if="item.title.indexOf(searchValue) > -1"
                >{{ item.title.substr(0, item.title.indexOf(searchValue))
                }}<span style="color: #f50">{{ searchValue }}</span
                >{{
                  item.title.substr(
                    item.title.indexOf(searchValue) + searchValue.length
                  )
                }}</span
              >
              <span v-else class="node-title">{{ item.title }} </span>
              <span
                class="icon-wrap"
                v-if="item.isLeaf && viewAble(item.type)"
                title="预览"
                @click="view(item)"
              >
                <a-icon type="file-search" />
              </span>
            </div>
          </template>
        </a-tree>
        <a-empty v-else />
      </div>
    </a-modal>
    <a-modal
      :visible="showSop"
      :footer="null"
      :width="1366"
      title="SOP查看"
      :destroyOnClose="true"
      @cancel="showSop = false"
    >
      <div style="height: 600px">
        <sop-list :parentCode="parentCode"></sop-list>
      </div>
    </a-modal>
    <!-- <a-modal
      :width="600"
      :visible="previewVisible"
      :footer="null"
      @cancel="handleCancel"
    >
      <img alt="" style="width: 100%" :src="imgUrl" />
    </a-modal> -->
    <!-- <viewer :images="imgUrl">
      <img alt="" style="width: 100%" :src="imgUrl" />
    </viewer> -->
  </div>
</template>
<script>
import ImageView from "@/pages/components/ImageView";
import FilesTable from "@/pages/components/filesTable";
import DiffList from "@/pages/components/diffList";
import checkPermit from "@/assets/js/tools/checkPermit";
import PERMITS from "@/assets/js/const/const_permits";
import Pop from "../../software/pop";
import offset from "@/assets/js/tools/offset";
import DownloadReason from "@/pages/components/downloadReason";
import DownloadLogs from "@/pages/components/downloadLogs";
import SopList from "@/pages/resourceManage/sopFile/sopList.vue";
import List from "./list";
import getWebUrl from "@/assets/js/tools/getWebUrl";
import { getVersions, zipDownload, getBomHistoryAndMaterial } from "./apis";
import { setpHistoryEnable } from "../apis";
import {
  getVersions,
  zipDownload,
  getBomHistoryAndMaterial,
  compare,
  getOriginalZip,
  checkExist,
  getzipAndRarInfo,
  decompress,
} from "./apis";
import {
  searchDefaultMailUser,
  sendMail,
} from "../../../components/emailCard/apis";
import { getUserList } from "../../../permission/apis";
import { getRoleUser } from "@/pages/user/apis";
import { setpHistoryEnable, getLockedList, getLogList } from "../apis";
import { dwgReview } from "@/pages/workplace/apis";
import { downloadLog } from "@/pages/system/logs/apis";
import { downLoadFileByFilePath } from "@/assets/js/apis";
import { mapGetters } from "vuex";
import createWs from "@/assets/js/websocket";
const WSMixin = createWs("version");
export default {
  name: "",
  mixins: [WSMixin],
  data() {
    return {
      treeReset: true,
      searchValue: "",
      imgUrl: "",
      previewVisible: false,
      showSop: false,
      fileTreeVisible: false,
      fileTree: [],
      fileTreeViewVisible: false,
      fileTreeView: [],
      userListAll: [],
      mailList: [],
      tester: [],
      oprateInfo: "",
      downloadlogVisible: false,
      downloadReasonVisible: false,
      popVisible: false,
      popPosition: {
        x: 0,
        y: 0,
        dir: "bottom",
      },
      popInfo: [],
      originalZipUrl: "",
      // 当前操作 下载bom包 下载原始包
      currentAction: "",
      sideVisible: true,
      logVisible: false,
      logList: [],
      lockListVisible: false,
      bomLockList: [],
      otherLockList: [],
      otherDocVisible: false,
      reasonVisible: false,
      reason: "",
      lastId: undefined,
      diffShow: false,
      diffData: [],
      softwareDownloadShow: false,
      parentModel: this.$route.query.parentModel,
      parentCode: this.$route.query.parentCode,
      customCode: this.$route.query.customCode,
      parentModel: this.$route.query.parentModel,
      parentName: this.$route.query.parentName,
      versionList: [],
      info: {},
      webUrl: getWebUrl(),
      record: {},
      currentVersion: -1,
      // 0120的锁定激活只能操作版本列表中第一个(最大版本号)
      // 最新的版本的id
      maxVersionid: -1,
      spinning: false,
      loading: false,
      total: 0,
      y: 400,
      update: -1,
      columns: [
        {
          fixed: "left",
          title: "序号",
          dataIndex: "dataIndex",
          key: "dataIndex",
          align: "center",
          width: 60,
          noSearch: true,
          scopedSlots: { customRender: "dataIndex" },
        },
        {
          title: "类别",
          dataIndex: "category",
@@ -211,7 +667,12 @@
        {
          title: "生产商",
          dataIndex: "producer",
          key: "producer",
          align: "center",
          width: 80,
        },
        {
          title: "厂家规格",
          dataIndex: "producerSpecification",
          align: "center",
          width: 80,
        },
@@ -273,7 +734,7 @@
          key: "operation",
          align: "center",
          fixed: "right",
          width: 100,
          width: 180,
          scopedSlots: { customRender: "action" },
        },
      ],
@@ -283,15 +744,24 @@
      softwareColumns: [
        {
          title: "软件名称",
          dataIndex: "softwareName",
          dataIndex: "fileName",
          align: "center",
          customCell: this.customCell,
          // width: 180,
        },
        {
          title: "上传时间",
          dataIndex: "submitTime",
          title: "版本",
          dataIndex: "version",
          align: "center",
          width: 180,
          customCell: this.customCell,
        },
        {
          title: "发布说明",
          dataIndex: "releaseNotes",
          align: "center",
          width: 340,
          customCell: this.customCell,
        },
        {
          title: "操作",
@@ -301,19 +771,97 @@
          scopedSlots: { customRender: "action" },
        },
      ],
      bomLockColumns: [
        {
          title: "子件名称",
          dataIndex: "subName",
          align: "center",
        },
        {
          title: "文件名称",
          dataIndex: "attachName",
          align: "center",
        },
        {
          title: "锁定说明",
          dataIndex: "localReason",
          align: "center",
        },
      ],
      otherLockColumns: [
        {
          title: "文件名称",
          dataIndex: "attachName",
          align: "center",
        },
        {
          title: "锁定说明",
          dataIndex: "localReason",
          align: "center",
        },
      ],
    };
  },
  components: {
    List,
    ImageView,
    FilesTable,
    DiffList,
    Pop,
    DownloadReason,
    DownloadLogs,
    SopList,
  },
  computed: {
    ...mapGetters("setting", ["affixed"]),
    ...mapGetters("account", ["permits", "user"]),
    canDownloadSoftware() {
      return checkPermit(PERMITS.downloadSoftware, this.permits);
    },
    canDownloadBom() {
      return checkPermit(PERMITS.downloadBom, this.permits);
    },
    canDownloadOriginBom() {
      return checkPermit(PERMITS.downloadOriginBom, this.permits);
    },
    canDownloadDoc() {
      return checkPermit(PERMITS.downloadDoc, this.permits);
    },
    canViewDoc() {
      return checkPermit(PERMITS.viewDoc, this.permits);
    },
    canLockBom() {
      return checkPermit(PERMITS.lockBom, this.permits);
    },
    is0120() {
      return /^0120/.test(this.parentCode);
    },
    isTester() {
      return this.tester.some((v) => v == this.user.name);
    },
    // 有状态为-1的版本在
    hasLock() {
      return this.versionList.some((v) => v.enabled == -1);
    },
    fileTreeViewRes() {
      let resList = this.fileTreeView.filter((v) => {
        let fileName = v.split("\\").pop();
        return fileName.indexOf(this.searchValue) > -1;
      });
      let {
        children: [
          {
            children: [{ children: list }],
          },
        ],
      } = this.format(resList, false);
      return list;
    },
  },
  watch: {
    update(n) {
      if (-1 != n) {
      if (-1 != n && !this._inactive) {
        this.$nextTick(() => {
          const wraper = this.$refs.wraper;
          const wraperH = wraper.clientHeight;
@@ -338,28 +886,150 @@
    },
  },
  methods: {
    getVersions() {
      getVersions(this.parentModel, this.customCode).then((res) => {
        res = res.data;
    viewSopFile() {
      this.showSop = true;
    },
    getRoleUser() {
      getRoleUser(1)
        .then((res) => {
          let { code, data, data2 } = res.data;
          let list = [];
          if (code && data) {
            // console.log(data);
            list = data2.map((v) => v.name);
          }
          this.tester = list;
        })
        .catch((err) => {
          console.log(err);
        });
    },
    downloadLogs() {
      let { customCode, parentCode } = this;
      this.oprateInfo = parentCode + "_" + customCode;
      this.downloadlogVisible = true;
    },
    customCell(record) {
      return {
        on: {
          mouseenter: (e) => this.cellMouseenter(e, record),
          mouseleave: (e) => this.cellMouseleave(e, record),
        },
      };
    },
    cellMouseenter(e, obj) {
      // console.log("enter", e, obj);
      const wraper = this.$refs.softwareTable.$el;
      // console.log(wraper, '=====wraper');
      const { clientHeight, clientWidth } = document.body;
      const { target, clientX, clientY } = e;
      let { left: x, top: y } = offset(wraper);
      x = clientX - x + 24;
      y = clientY - y + 55 + 24;
      let dir = "bottom";
      if (clientHeight < 380 * 2) {
        if (x + 420 + 18 > clientWidth) {
          dir = "left";
        } else {
          dir = "right";
        }
        if (y < 180) {
          y = 180;
        } else if (y > clientHeight - 378) {
          y = clientHeight / 2;
        }
      } else {
        if (y + 18 + 360 > clientHeight) {
          // y = clientHeight - 378;
          dir = "top";
        } else {
          dir = "bottom";
        }
        if (x < 300) {
          x = 300;
        }
        if (x + 300 > clientWidth) {
          x = clientWidth - 300;
        }
      }
      this.popPosition.x = x;
      this.popPosition.y = y;
      this.popPosition.dir = dir;
      this.popInfo = [obj];
      this.popVisible = true;
    },
    cellMouseleave(e, obj) {
      // console.log("leave", obj);
      this.popVisible = false;
    },
    getVersions(res) {
      if (res) {
        // res = res.data;
        const { code, data, data2 } = res;
        let list = [];
        let maxVersionid = -1;
        if (code && data) {
          list = data2;
          list.forEach((v) => {
            maxVersionid = v.id > maxVersionid ? v.id : maxVersionid;
          });
        }
        this.maxVersionid = maxVersionid;
        this.versionList = list;
      }
    },
    onWSOpen() {
      this.$nextTick(() => {
        this.sendMessage();
      });
    },
    sendMessage() {
      if (!this.isWSOpen) {
        return false;
      }
      const { parentCode, customCode } = this;
      let data = {
        parentCode,
        customCode,
      };
      // 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.getVersions(res);
    },
    selectChanged(obj) {
      // console.log(obj, "--==");
      // this.record = obj;
      this.currentVersion = obj;
      this.getOriginalZip();
      this.getInfo();
    },
    getInfo() {
    getOriginalZip() {
      const {
        currentVersion: { parentCode, parentModel, customCode, version },
      } = this;
      this.originalZipUrl = "";
      getOriginalZip(parentCode, parentModel, customCode, version).then(
        (res) => {
          const { code, data, data2 } = res.data;
          if (code && data) {
            this.originalZipUrl = data2;
          }
        }
      );
    },
    getInfo(force) {
      const {
        currentVersion: { id, version },
        info,
        lastId,
      } = this;
      if (id == lastId && !force) {
        return false;
      }
      this.lastId = id;
      this.spinning = true;
      getBomHistoryAndMaterial(id, version).then((res) => {
        let { code, data, data2, data3, data4 } = res.data;
@@ -367,20 +1037,29 @@
        let list = [];
        let softwareList = [];
        let otherDoc = [];
        let attachLocks = [];
        this.spinning = false;
        let oprateInfo;
        if (code && data) {
          list = data2.map((v) => {
            if (v.materials && v.materials.length) {
              v.children = v.materials;
              v.children = v.materials.map((vv) => {
                vv.p_subCode = v.subCode;
                return vv;
              });
            }
            return v;
          });
          softwareList = data3;
          otherDoc = data4;
          otherDoc = data4.files || [];
          attachLocks = data4.attachLocks || [];
          let { parentCode, customCode } = data2[0];
          oprateInfo = parentCode + "_" + customCode;
        }
        this.dataSource = list;
        this.softwareList = softwareList;
        this.otherDoc = otherDoc;
        this.info = { attachLocks, productId: id, oprateInfo };
        if (-1 == this.update) {
          this.update = Math.random();
        }
@@ -403,32 +1082,28 @@
          console.log(error);
        });
    },
    downloadLog(record) {
      const { parentModel, subModel, dwgUrl, softwareUrl } = record;
      const url = softwareUrl
        ? this.webUrl + softwareUrl
        : this.webUrl + dwgUrl;
      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);
      downloadLog(parentModel, subModel);
    showReason(action, record) {
      const { dwgUrl, fileUrl: softwareUrl } = record;
      const url = softwareUrl ? softwareUrl : dwgUrl;
      this.downloadUrl = url;
      this.currentAction = action;
      this.downloadReasonVisible = true;
    },
    zipDownload() {
      // const { parentModel, currentVersion } = this;
      const {
        currentVersion: { id, version },
      } = this;
      zipDownload(id, version).then((res) => {
        // console.log(res, "===========");
        let { headers, data, status } = res;
    downloadLog(reason) {
      let url = this.downloadUrl;
      let reg = /(.*\\+)*(.*)$/;
      let fileName = url.match(reg)[2];
      downLoadFileByFilePath(
        url,
        reason,
        this.info.oprateInfo,
        this.currentVersion.versionTime,
        12
      ).then((res) => {
        let { 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() : "未知文件名.zip";
          let link = document.createElement("a");
          link.style.display = "none";
          link.href = url;
@@ -438,6 +1113,111 @@
          document.body.removeChild(link);
          window.URL.revokeObjectURL(url);
        } else {
          this.$message.error("下载失败");
        }
      });
    },
    // action 检查后要执行的动作
    checkLock(action) {
      this.currentAction = action;
      getLockedList(this.currentVersion.id).then((res) => {
        const { code, data, data2, data3 } = res.data;
        if (code) {
          if (data) {
            // 有锁定
            this.bomLockList = data2;
            this.otherLockList = data3;
            this.lockListVisible = true;
          } else {
            // 没有锁定
            // this.zipDownload();
            // this[action]();
            this.showReasonDialog();
          }
        } else {
          this.$message.error("查询锁定清单出错");
        }
      });
    },
    lockListCancel() {
      this.lockListVisible = false;
    },
    lockListOk() {
      this.lockListVisible = false;
      // this.zipDownload();
      // this[this.currentAction]();
      this.downloadReasonVisible = true;
    },
    showReasonDialog() {
      // this.currentAction = action;
      this.downloadReasonVisible = true;
    },
    downloadReasonOk(reason) {
      this[this.currentAction](reason);
    },
    OriginalZipDownload(reason) {
      // let loading = this.$layer.loading();
      // let link = document.createElement("a");
      // link.style.display = "none";
      // let url = this.webUrl + this.originalZipUrl;
      // link.href = url;
      // link.download = url;
      // document.body.appendChild(link);
      // link.click();
      // this.$layer.close(loading);
      // document.body.removeChild(link);
      let url = this.originalZipUrl;
      let reg = /(.*\\+)*(.*)$/;
      let fileName = url.match(reg)[2];
      downLoadFileByFilePath(
        url,
        reason,
        this.info.oprateInfo,
        this.currentVersion.versionTime,
        12
      ).then((res) => {
        let { data, status } = res;
        if (200 == status && data) {
          let url = window.URL.createObjectURL(data);
          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("下载失败");
        }
      });
    },
    zipDownload(reason) {
      // const { parentCode, currentVersion } = this;
      const {
        currentVersion: { id, version },
      } = this;
      let loading = this.$layer.loading();
      zipDownload(id, version, reason, this.info.oprateInfo).then((res) => {
        // console.log(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
            ? decodeURI(matchRes[1].trim())
            : "未知文件名.zip";
          let link = document.createElement("a");
          link.style.display = "none";
          link.href = url;
          link.download = fileName;
          document.body.appendChild(link);
          link.click();
          this.$layer.close(loading);
          document.body.removeChild(link);
          window.URL.revokeObjectURL(url);
        } else {
          this.$layer.close(loading);
          this.$message.error("操作失败");
        }
      });
@@ -452,29 +1232,403 @@
      this.softwareDownloadShow = false;
    },
    diff(data) {
      console.log("比较两个版本", data);
      // console.log("比较两个版本", data);
      let params = data.map((v) => v * 1).sort((a, b) => a - b);
      compare(...params).then((res) => {
        const { code, data, data2, data3 } = res.data;
        if (code) {
          this.diffData = {
            addList: data2.map((v) => v.materialObj),
            delList: data3.map((v) => v.materialObj),
            updateList: data,
          };
          this.diffShow = true;
        } else {
          this.$message.error("比较差异失败");
        }
      });
    },
    diffCancel() {
      this.diffShow = false;
    },
    changeStatus() {
      if (this.hasLock && this.currentVersion.enabled != -1) {
        this.$message.error("存在新上传的版本未复核, 不能操作其他版本");
        return false;
      }
      this.reasonVisible = true;
    },
    reasonCancel() {
      this.reasonVisible = false;
    },
    reasonOk() {
      let {
        currentVersion: { customCode, parentCode, enabled, version },
        currentVersion: {
          customCode,
          parentCode,
          enabled: lockFlagNow,
          version,
          versionTime,
        },
        reason,
      } = this;
      enabled = !enabled * 1;
      setpHistoryEnable({customCode, parentCode, enabled, version}).then((res) => {
        let {
          code,
          data,
        } = res.data;
      let enabled = lockFlagNow != 1 ? 1 : 0;
      setpHistoryEnable({
        customCode,
        parentCode,
        enabled,
        version,
        reason,
        versionTime,
      }).then((res) => {
        let { code, data } = res.data;
        if (code && data) {
          this.$message.success('操作成功');
          this.$message.success("操作成功");
          this.currentVersion.enabled = enabled;
          // 如果是测试人员解锁确认 则自动发送邮件
          if (lockFlagNow == -1 && this.isTester) {
            this.sendEmail();
          }
          this.reasonVisible = false;
        } else {
          this.$message.error('操作失败');
          this.$message.error("操作失败");
        }
      });
    },
    sendEmail() {
      let { title, content } = this.handleEmailShow(this.currentVersion, true);
      let params = { mailList: this.mailList, title, content };
      sendMail(params);
    },
    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为3
      let type = 3;
      searchDefaultMailUser(type).then((res) => {
        let rs = res.data;
        let data = [];
        if (rs.code === 1) {
          data = rs.data.map((item) => {
            return item.user;
          });
        }
        this.mailList = this.userListAll
          .filter((o) => data.includes(o.name) && o.mail)
          .map((item) => {
            return item.mail;
          });
      });
    },
    handleEmailShow(record, get) {
      let title =
        "[产品发布记录]" + record.parentModel + " 版本:" + record.versionTime;
      let content = [];
      content.push("物料编码: " + record.parentCode);
      content.push("物料名称: " + record.parentName);
      content.push("规格型号: " + record.parentModel);
      content.push("标准机型: " + (!record.customCode ? "是" : "否"));
      content.push("定制单号: " + (record.customCode || "无"));
      content.push("版本时间: " + record.versionTime);
      if (get) {
        return {
          title,
          content: content.join("\n"),
        };
      }
    },
    showOtherDoc() {
      this.getInfo(1);
      this.otherDocVisible = true;
    },
    otherDocCancel() {
      this.otherDocVisible = false;
    },
    viewLog() {
      getLogList({
        parentCode: this.parentCode,
        customCode: this.customCode,
      }).then((res) => {
        const { code, data } = res.data;
        if (code) {
          this.logList = data;
          this.logVisible = true;
        } else {
          this.$message.error("日志查询失败");
        }
      });
    },
    logCancel() {
      this.logVisible = false;
    },
    toggleSide() {
      this.sideVisible = !this.sideVisible;
    },
    rowClassFn(record) {
      let classList = [];
      if (record.children && record.children.length) {
        classList.push("is-replace");
      }
      // 0120是半成品
      if (/^0120/.test(record.subCode)) {
        classList.push("is-0120");
      }
      // 08 09 开头的半成品
      if (/^08|^09/.test(record.subCode)) {
        classList.push("is-08_09");
      }
      return classList;
    },
    toDetails0120(record) {
      const {
        subCode: parentCode,
        subModel: parentModel,
        subName: parentName,
      } = record;
      this.$router.push({
        path: "/resource/product-details-0120",
        query: { parentCode, customCode: "", parentModel, parentName },
      });
    },
    toDetails_type(record, type) {
      const {
        subCode: parentCode,
        subModel: parentModel,
        subName: parentName,
      } = record;
      checkExist(parentCode, "")
        .then((res) => {
          let { code, data } = res.data;
          if (code && data) {
            this.$router.push({
              path: "/resource/product-details-" + type,
              query: { parentCode, customCode: "", parentModel, parentName },
            });
            // console.log(data);
          } else {
            this.$message.error("产品未上传");
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    getParent(obj, arr, i) {
      let len = arr.length - 1;
      if (i > len) {
        return false;
      }
      // if (i == 0) {
      //   return obj;
      // }
      // if (i == 1) {
      //   return obj[arr[0]];
      // }
      let res = obj;
      let tmp = {};
      for (let m = 1; m <= i; m++) {
        // tmp[arr[m]] = {};
        // tmp = tmp[arr[m]];
        tmp = arr[m - 1];
        res = res[tmp];
      }
      return res;
    },
    format(list, needTime = true) {
      let counter = this.counter();
      let obj = {
        counter,
        key: "root",
        children: [],
      };
      // let root = {
      //   counter,
      //   key: "root",
      //   children: [],
      // };
      list.forEach((v) => {
        let url = needTime ? v.fileName : v;
        let fileTime = v.fileTime;
        let splitStr = needTime ? "/" : "\\";
        let arr = url.split(splitStr);
        let fileName = arr.pop();
        let files = fileName.split(".");
        let type = files.length ? files[files.length - 1].toLowerCase() : "";
        let tmp = obj;
        for (let i = 0, len = arr.length; i < len; i++) {
          let parent = this.getParent(obj, arr, i);
          if (!tmp[arr[i]]) {
            tmp[arr[i]] = {
              parent,
              title: arr[i],
              key: parent.key + "-" + parent.counter(),
              children: [],
              scopedSlots: { title: "custom" },
              counter: this.counter(),
            };
            // tmp[arr[i]].parent.children.push(tmp[arr[i]]);
            parent.children.push(tmp[arr[i]]);
          }
          tmp = tmp[arr[i]];
        }
        // console.log(tmp.children, 'children')
        tmp.children.push({
          url,
          type,
          fileName,
          fileTime,
          key: tmp.key + "-" + tmp.counter(),
          title: needTime ? fileName + "  " + fileTime : fileName,
          scopedSlots: { title: "custom" },
          isLeaf: true,
        });
        // console.log(obj, '......')
        // for (let i = 0, len = arr.length; i < len; i++) {
        //   // 判断有没有父级 i为0没有 i为大于1就有
        //   let parent = i > 0 ? tmp[arr[i - 1]] : root;
        //   if (!obj[item]) {
        //     obj[item] = {
        //       title: item,
        //       key: parent.key + "-" + parent.counter(),
        //       children: [],
        //       counter: this.counter(),
        //     };
        //   }
        // }
      });
      return obj;
    },
    counter() {
      let count = 0;
      return () => {
        return count++;
      };
    },
    // 查看压缩包内文件目录结构
    viewRar() {
      let loading = this.$layer.loading();
      // console.log(this.originalZipUrl);
      getzipAndRarInfo(this.originalZipUrl)
        .then((res) => {
          let { code, data, data2 } = res.data;
          let list = { children: [] };
          if (code && data) {
            console.log(data2);
            list = this.format(data2);
          }
          this.$layer.close(loading);
          this.fileTree = list.children;
          this.fileTreeVisible = true;
          // { title: 'Tree Node', key: '2', isLeaf: true },
          console.log(list, "===list");
        })
        .catch((err) => {
          this.$layer.close(loading);
          console.log(err);
        });
    },
    // 解压压缩包到服务器 然后返回内部文件的结构和路径 路径可用来预览
    preview() {
      this.searchValue = '';
      let loading = this.$layer.loading();
      decompress(this.originalZipUrl)
        .then((res) => {
          let { code, data, data2, msg } = res.data;
          let list = [];
          if (code && data) {
            // console.log(data);
            this.$message.success("解析成功");
            // let {
            //   children: [
            //     {
            //       children: [{ children: list1 }],
            //     },
            //   ],
            // } = this.format(data2, false);
            // list = list1;
            list = data2;
          } else {
            this.$message.error(msg);
          }
          this.$layer.close(loading);
          this.fileTreeView = list;
          // this.fileTreeView = list.children[0]["children"][0]["children"];
          this.fileTreeViewVisible = true;
          // console.log(list, "===list111");
        })
        .catch((err) => {
          console.log(err);
          this.$layer.close(loading);
        });
    },
    viewAble(type) {
      return ["bmp", "jpg", "jpeg", "png", "pdf", "doc", "docx", "dwg"].some(
        (v) => v == type
      );
    },
    view(obj) {
      switch (obj.type) {
        // 图片
        case "bmp":
        case "jpg":
        case "jpeg":
        case "png":
          this.imgUrl = this.webUrl + obj.url;
          this.viewerImg();
          // 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;
    },
    viewerImg() {
      this.$viewerApi({
        images: [this.imgUrl],
        options: {
          initialViewIndex: 0,
        },
      });
    },
    searchChanged() {
      this.treeReset = false;
      this.$nextTick(() => {
        this.treeReset = true;
      });
    },
  },
  mounted() {
    this.getVersions();
    this.searchAllUserList();
    this.getRoleUser();
    this.sendMessage();
    // this.searchData();
    window.addEventListener("resize", this.resize);
  },
@@ -487,6 +1641,48 @@
<style scoped lang="less">
.main {
  height: 100%;
  display: flex;
  flex-direction: row;
  overflow: hidden;
  .side-content {
    position: relative;
    transition: width 0.5s ease;
    width: 200px;
    z-index: 1;
    .side-inner {
      height: 100%;
      width: 200px;
      position: absolute;
      top: 0;
      right: 0;
      /deep/.ant-card-body {
        padding: 10px;
      }
    }
    .handler {
      position: absolute;
      left: 100%;
      top: 50%;
      width: 14px;
      height: 20px;
      background: #ccc;
      color: #aaa;
      transform: translate(0, -50%);
      .icon {
        transition: transform 0.3s ease;
        transition-delay: 0.2;
      }
    }
    &.hide {
      width: 0;
      .handler .icon {
        transform: rotate(180deg);
      }
    }
  }
  .main-content {
    flex: 1;
  }
  .ant-layout-header,
  .ant-layout-sider {
    background: transparent;
@@ -510,6 +1706,9 @@
  }
  /deep/.ant-layout {
    margin-left: 10px;
  }
  /deep/.hide + .ant-layout {
    margin-left: 0;
  }
  .ant-layout-header {
    padding: 0;
@@ -549,6 +1748,7 @@
  .img-wraper {
    width: 80px;
    height: 50px;
    display: inline-block;
    .image-view {
      width: 100%;
      height: 100%;
@@ -573,5 +1773,73 @@
  /deep/.ant-table-row-level-1.ant-table-row-level-1:hover > td {
    background: #ffbcc9;
  }
  /deep/.is-08_09 > td,
  /deep/.is-0120 > td {
    background: #ffae00;
  }
  /deep/.is-08_09.is-08_09.ant-table-row-hover > td,
  /deep/.is-08_09.is-08_09:hover > td,
  /deep/.is-0120.is-0120.ant-table-row-hover > td,
  /deep/.is-0120.is-0120:hover > td {
    background: #f8c34f;
  }
}
</style>
/deep/.ant-descriptions-row > th,
/deep/.ant-descriptions-row > td {
  padding-bottom: 0;
}
.table-title {
  font-weight: 700;
  color: #13c2c2;
}
.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;
  }
}
/deep/.ant-descriptions-row .ant-descriptions-item:first-child {
  width: 180px;
}
.file-tree-view /deep/ li {
  // display: flex;
  // flex-direction: row;
  .ant-tree-switcher {
    float: left;
  }
  .ant-tree-node-content-wrapper {
    display: block;
    cursor: auto;
    .ant-tree-title {
      display: block;
    }
  }
  .ant-tree-child-tree {
    flex-basis: 100%;
  }
  .icon-wrap {
    cursor: pointer;
    font-size: 22px;
  }
}
.flex-r {
  display: flex;
  flex-direction: row;
  &.space-between {
    justify-content: space-between;
  }
}
</style>