研发图纸文件管理系统-前端项目
he wei
2023-07-05 722829db442d0b93aaa99fd7722b00f4d206dfec
U 修改sop锁定解锁
2个文件已添加
3个文件已修改
779 ■■■■■ 已修改文件
src/pages/resourceManage/sopFile/apis.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/list.vue 198 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/sop-history.vue 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/versionList.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/config.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/apis.js
@@ -80,4 +80,51 @@
      type1
    }
  })
}
/**
 * 锁定 解锁
 * @param {*} sopId
 * @param {*} status 0 锁定 1 解锁
 * @param {*} reason 操作原因
 * @returns
 */
export const sopLock = (sopId, status, reason) => {
  return axios({
    method: "PUT",
    url: "sop/updateStatusById",
    data: {
      sopId,
      status,
      reason
    }
  })
}
/**
 * 锁定日志
 * @param {*} sopId
 */
export const getLogList = (sopId) => {
  return axios({
    method: "GET",
    url: "sopLockLog/getSopLockLog",
    params: {
      sopId
    }
  })
}
/**
 * 查询sop历史版本
 * @param {*} fileName
 * @returns
 */
export const getSopHis = (fileName) => {
  return axios({
    method: "GET",
    url: "sop/getSopHis",
    params: {
      fileName
    }
  })
}
src/pages/resourceManage/sopFile/list.vue
@@ -60,26 +60,36 @@
              <a-tag v-else class="tag-all" color="#f50"> 适用全部 </a-tag>
              <template v-if="canDownload">
                <a-divider type="vertical"></a-divider>
                <a @click="downloadFile(record)">下载</a>
                <a :disabled="!record.status" @click="downloadFile(record)"
                  >下载</a
                >
              </template>
              <a-divider type="vertical"></a-divider>
              <a-popover title="" trigger="hover">
                <a-space v-if="canUpload" class="btn-grp" direction="vertical" slot="content">
                <a-space class="btn-grp" direction="vertical" slot="content">
                  <a-button
                    v-if="canUpload"
                    type="primary"
                    @click="updateDesc(record)"
                    >更新说明</a-button
                  >
                  <!-- <a-button
                    v-if="canLock"
                  <a-button
                    v-if="canUpload"
                    type="primary"
                    @click="lock(record)"
                    >{{ record.lockFlag ? "解锁" : "锁定" }}</a-button
                  > -->
                    >{{ record.status == 0 ? "解锁" : "锁定" }}</a-button
                  >
                  <a-button
                    v-if="canUpload"
                    type="primary"
                    @click="handleEmailShow(record)"
                    >邮件通知</a-button
                  >
                  <a-button type="primary" @click="viewLog(record)"
                    >锁定日志</a-button
                  >
                  <a-button type="primary" @click="goHistory(record)"
                    >历史版本</a-button
                  >
                </a-space>
                <a>更多</a>
@@ -164,6 +174,52 @@
        v-if="emailShow"
      ></email-card>
    </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="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.status == 0 ? 'red' : 'green'"
          >
            <div>
              <span class="user">{{ item.userName }}</span> 在
              <span class="time">{{ item.createTime }}</span>
              {{ item.status == 0 ? "锁定" : "解锁" }}了版本
              <span class="version">{{ item.fileVersion }}</span>
            </div>
            <div>操作原因: {{ item.reason ? item.reason : "无" }}</div>
          </a-timeline-item>
        </a-timeline>
        <a-empty v-else />
      </div>
    </a-modal>
  </div>
</template>
@@ -182,6 +238,8 @@
  getSopType1,
  getSopType2,
  updateSop,
  sopLock,
  getLogList,
} from "./apis";
import offset from "@/assets/js/tools/offset";
import Pop from "./pop";
@@ -197,6 +255,11 @@
  name: "list",
  data() {
    return {
      logList: [],
      logVisible: false,
      currentObj: null,
      reasonVisible: false,
      reason: "",
      emailShow: false,
      emailInfo: {
        title: "",
@@ -365,9 +428,6 @@
    canDownload() {
      return checkPermit(PERMITS.downloadSoftware, this.permits);
    },
    canLock() {
      return checkPermit(PERMITS.lockOther, this.permits);
    },
  },
  watch: {
    update(n) {
@@ -435,20 +495,39 @@
    handleEmailShow(record) {
      this.emailInfo.title =
        "[sop发布记录]" + record.fileName + " 版本号:" + record.fileVersion;
            let sopProductListText = record.sopProductList.map(item=>{
                return "物料编码:"+item.code+" 型/板号:"+item.model;
            }).join("\n");
      let sopProductListText = record.sopProductList
        .map((item) => {
          return "物料编码:" + item.code + " 型/板号:" + item.model;
        })
        .join("\n");
      this.emailInfo.content =
      "文件基础信息\n" +
      "文件名称:"+record.fileName+"\n"+
      "文件类型:"+record.fileType+"\n"+
      "文件版本:"+record.fileVersion+"\n"+
      "关联版本:"+record.fileRelatedVersion+"\n"+
      "编制:"+record.editor+"\n"+
      "审核:"+record.auditor+"\n"+
      "发布日期:"+record.releaseDate+"\n"+
      "文件适用产品\n"+sopProductListText+"\n"+
      "发布说明:"+record.releaseNotes;
        "文件基础信息\n" +
        "文件名称:" +
        record.fileName +
        "\n" +
        "文件类型:" +
        record.fileType +
        "\n" +
        "文件版本:" +
        record.fileVersion +
        "\n" +
        "关联版本:" +
        record.fileRelatedVersion +
        "\n" +
        "编制:" +
        record.editor +
        "\n" +
        "审核:" +
        record.auditor +
        "\n" +
        "发布日期:" +
        record.releaseDate +
        "\n" +
        "文件适用产品\n" +
        sopProductListText +
        "\n" +
        "发布说明:" +
        record.releaseNotes;
      this.emailShow = true;
    },
    emailCancel() {
@@ -648,14 +727,14 @@
      fileParse(formData)
        .then((res) => {
          this.$layer.close(loading);
          let { code, data, data2 } = res.data;
          let { code, data, data2, msg } = res.data;
          if (code && data) {
            let rowId = this.resObj.rowId;
            this.resObj = data2;
            this.resObj.id = rowId;
            this.$message.success("解析成功");
          } else {
            this.$message.error("解析失败");
            this.$message.error(msg);
          }
        })
        .catch((error) => {
@@ -756,13 +835,59 @@
      let link = document.createElement("a");
      link.style.display = "none";
      let url = this.webUrl + record.fileUrl;
      let fileName = record.fileUrl.split('/').pop();
      let fileName = record.fileUrl.split("/").pop();
      link.href = url;
      link.download = fileName;
      document.body.appendChild(link);
      link.click();
      this.$layer.close(loading);
      document.body.removeChild(link);
    },
    reasonCancel() {
      this.reasonVisible = false;
    },
    reasonOk() {
      let { id, status } = this.currentObj;
      let reason = this.reason;
      status = status == 0 ? 1 : 0;
      sopLock(id, status, reason).then((res) => {
        const { code } = res.data;
        if (code) {
          this.$message.success("操作成功");
          this.reasonVisible = false;
          this.searchData();
        } else {
          this.$message.error("操作失败");
        }
      });
    },
    lock(record) {
      this.reason = "";
      this.currentObj = record;
      this.reasonVisible = true;
    },
    viewLog(obj) {
      // console.log(obj);
      const { id, fileVersion } = obj;
      getLogList(id).then((res) => {
        const { code, data, data2 } = res.data;
        if (code) {
          this.logList = data2.map((v) => ({ ...v, fileVersion }));
          this.logVisible = true;
        } else {
          this.$message.error("日志查询失败");
        }
      });
    },
    logCancel() {
      this.logVisible = false;
    },
    goHistory(record) {
      let { fileName } = record;
      this.$router.push({
        path: "/resource/sop-history",
        query: { fileName },
      });
    },
  },
  mounted() {
@@ -849,4 +974,27 @@
    overflow-x: hidden;
  }
}
.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;
  }
}
</style>
src/pages/resourceManage/sopFile/sop-history.vue
New file
@@ -0,0 +1,393 @@
<template>
  <div class="main">
    <a-layout class="main">
      <a-layout-sider width="260">
        <list class="list" :list="versionList" @select="selectChanged"></list>
      </a-layout-sider>
      <a-layout>
        <a-layout-header>
          <a-card>
            <a-descriptions title="详情" bordered>
              <a-descriptions-item label="文件名称">{{
                currentVersion.fileName
              }}</a-descriptions-item>
              <a-descriptions-item label="版本号">{{
                currentVersion.fileVersion
              }}</a-descriptions-item>
              <a-descriptions-item label="上传时间">{{
                currentVersion.createTime
              }}</a-descriptions-item>
              <a-descriptions-item label="负责人">{{
                currentVersion.editor
              }}</a-descriptions-item>
              <a-descriptions-item label="审核">{{
                currentVersion.auditor
              }}</a-descriptions-item>
              <a-descriptions-item label="文件类型">{{
                currentVersion.fileType
              }}</a-descriptions-item>
              <a-descriptions-item label="发布时间">{{
                currentVersion.releaseDate
              }}</a-descriptions-item>
              <a-descriptions-item label="发布说明">{{
                currentVersion.releaseNotes
              }}</a-descriptions-item>
            </a-descriptions>
          </a-card>
        </a-layout-header>
        <a-layout-content>
          <div class="wraper" ref="wraper">
            <div class="inner">
              <div class="title">适用产品</div>
              <a-table
                v-if="currentVersion.currentFlag == 0"
                ref="aTable"
                size="small"
                :scroll="{ y: 300 }"
                bordered
                :columns="columns"
                :data-source="dataSource"
                :pagination="false"
                rowKey="id"
              >
              </a-table>
              <div v-else>
                <a-tag color="#f50" class="all">适用全部</a-tag>
              </div>
            </div>
          </div>
        </a-layout-content>
        <a-layout-footer>
          <a-card>
            <template>
              <a-button v-if="canDownload && currentVersion.status" type="primary" @click="downloadFile"
                >下载</a-button
              >
              <a-button v-if="canUpload" type="primary" @click="lock">{{
                currentVersion.status == 0 ? "解锁" : "锁定"
              }}</a-button>
              <a-button type="primary" @click="viewLog">锁定日志</a-button>
            </template>
          </a-card>
        </a-layout-footer>
      </a-layout>
    </a-layout>
    <!-- 操作原因 -->
    <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="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.status == 0 ? 'red' : 'green'"
          >
            <div>
              <span class="user">{{ item.userName }}</span> 在
              <span class="time">{{ item.createTime }}</span>
              {{ item.status == 0 ? "锁定" : "解锁" }}了版本
              <span class="version">{{ item.fileVersion }}</span>
            </div>
            <div>操作原因: {{ item.reason ? item.reason : "无" }}</div>
          </a-timeline-item>
        </a-timeline>
        <a-empty v-else />
      </div>
    </a-modal>
  </div>
</template>
<script>
import List from "./versionList";
import getWebUrl from "@/assets/js/tools/getWebUrl";
import { sopLock, getSopHis, getLogList } from "./apis";
import checkPermit from "@/assets/js/tools/checkPermit";
import PERMITS from "@/assets/js/const/const_permits";
import { mapGetters } from 'vuex';
export default {
  name: "",
  data() {
    return {
      logList: [],
      logVisible: false,
      reasonVisible: false,
      reason: "",
      fileName: this.$route.query.fileName,
      versionList: [],
      webUrl: getWebUrl(),
      currentVersion: {},
      columns: [
        {
          title: "物料编码",
          dataIndex: "code",
          align: "center",
        },
        {
          title: "型/板号",
          dataIndex: "model",
          align: "center",
        },
      ],
      dataSource: [],
    };
  },
  components: {
    List,
  },
  computed: {
    ...mapGetters("account", ["permits"]),
    canUpload() {
      return checkPermit(PERMITS.uploadSoftware, this.permits);
    },
    canDownload() {
      return checkPermit(PERMITS.downloadSoftware, this.permits);
    },
  },
  watch: {},
  methods: {
    viewLog() {
      // console.log(obj);
      const { id, fileVersion } = this.currentVersion;
      getLogList(id).then((res) => {
        const { code, data, data2 } = res.data;
        if (code) {
          this.logList = data2.map((v) => ({ ...v, fileVersion }));
          this.logVisible = true;
        } else {
          this.$message.error("日志查询失败");
        }
      });
    },
    logCancel() {
      this.logVisible = false;
    },
    downloadFile() {
      // console.log(record);
      let record = this.currentVersion;
      let loading = this.$layer.loading();
      let link = document.createElement("a");
      link.style.display = "none";
      let url = this.webUrl + record.fileUrl;
      let fileName = record.fileUrl.split("/").pop();
      link.href = url;
      link.download = fileName;
      document.body.appendChild(link);
      link.click();
      this.$layer.close(loading);
      document.body.removeChild(link);
    },
    getVersions() {
      getSopHis(this.fileName).then((res) => {
        const { code, data, data2 } = res.data;
        let list = [];
        if (code && data) {
          list = data2;
        }
        this.versionList = list;
      });
    },
    selectChanged(obj) {
      // console.log(obj, "--==");
      this.currentVersion = obj;
      this.dataSource = obj.sopProductList;
    },
    reasonCancel() {
      this.reasonVisible = false;
    },
    reasonOk() {
      let { id, status } = this.currentVersion;
      let reason = this.reason;
      status = status == 0 ? 1 : 0;
      sopLock(id, status, reason).then((res) => {
        const { code } = res.data;
        if (code) {
          this.$message.success("操作成功");
          this.reasonVisible = false;
          this.currentVersion.status = status;
        } else {
          this.$message.error("操作失败");
        }
      });
    },
    lock() {
      this.reason = "";
      this.reasonVisible = true;
    },
  },
  mounted() {
    this.getVersions();
  },
  beforeDestroy() {},
};
</script>
<style scoped lang="less">
.main {
  height: 100%;
  .ant-layout-header,
  .ant-layout-sider {
    background: transparent;
  }
  .ant-layout-header {
    height: auto;
  }
  .list {
    height: 100%;
  }
  .wraper {
    height: 100%;
    position: relative;
    .inner {
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      bottom: 0;
    }
  }
  /deep/.ant-layout {
    margin-left: 10px;
  }
  .ant-layout-header {
    padding: 0;
    line-height: inherit;
    margin-bottom: 10px;
  }
  .ant-btn + .ant-btn {
    margin-left: 1em;
  }
  .ant-layout-content {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    color: rgba(0, 0, 0, 0.65);
    font-size: 14px;
    font-variant: tabular-nums;
    line-height: 1.5;
    list-style: none;
    -webkit-font-feature-settings: "tnum";
    font-feature-settings: "tnum";
    position: relative;
    background: #fff;
    border-radius: 2px;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
    padding: 24px;
    zoom: 1;
    /deep/.ant-descriptions-item-label {
      width: 12rem;
      text-align: right;
    }
  }
  .ant-layout-footer {
    padding: 0;
  }
  .img-wraper {
    width: 80px;
    height: 50px;
    display: inline-block;
    .image-view {
      width: 100%;
      height: 100%;
      /deep/img {
        width: 100%;
        height: 100%;
        object-fit: contain;
      }
    }
  }
  /deep/.is-replace > td {
    background: #00eaff;
  }
  /deep/.is-replace.is-replace.ant-table-row-hover > td,
  /deep/.is-replace.is-replace:hover > td {
    background: #affaff;
  }
  /deep/.ant-table-row-level-1 > td {
    background: #ff8ea2;
  }
  /deep/.ant-table-row-level-1.ant-table-row-level-1.ant-table-row-hover > td,
  /deep/.ant-table-row-level-1.ant-table-row-level-1:hover > td {
    background: #ffbcc9;
  }
  .title {
    margin-bottom: 20px;
    color: rgba(0, 0, 0, 0.85);
    font-weight: bold;
    font-size: 16px;
    line-height: 1.5;
  }
}
.diff-content {
  display: flex;
  flex-direction: column;
  height: 400px;
  .footer {
    text-align: right;
    .btn {
      padding: 6px;
      display: inline-block;
      background: #00eaff;
      border-radius: 4px;
      color: #fff;
    }
  }
  .img-wrap {
    flex: 1;
  }
  &.full {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    height: auto;
  }
}
.img-wrap {
  width: 100%;
  position: relative;
  .img-wrap-inner {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    img {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
  }
}
.all {
  padding: 10px 20px;
  font-size: 22px;
}
</style>
src/pages/resourceManage/sopFile/versionList.vue
New file
@@ -0,0 +1,130 @@
<template>
  <div class="posR">
    <div class="inner">
      <a-card class="main">
        <!-- 列表 -->
        <div class="contain">
          <div
            :class="['item', { selected: currentV == item.id }]"
            v-for="(item, idx) in list"
            :key="'item_' + idx"
            @click="selectHandle(item)"
          >
            <span :class="['status', { actived: item.status }]"></span>
            <div class="version">{{ item.fileVersion }}</div>
          </div>
        </div>
      </a-card>
    </div>
  </div>
</template>
<script>
export default {
  name: "",
  props: {
    list: {
      type: Array,
      default() {
        return [];
      },
    },
  },
  computed: {
    // data() {
    //   return this.list.filter((v) => {
    //     const reg = new RegExp(this.keyword, "i");
    //     return reg.test(v.subModel);
    //   });
    // },
  },
  watch: {
    list(n) {
      if (n.length) {
        this.selectHandle(this.list[0]);
      }
    },
  },
  data() {
    return {
      // keyword: '',
      currentV: "-1",
    };
  },
  components: {},
  methods: {
    selectHandle(item) {
      this.currentV = item.id;
      this.$emit("select", item);
    },
  },
  mounted() {},
};
</script>
<style scoped lang="less">
.posR {
  position: relative;
}
.inner {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}
.main {
  height: 100%;
  /deep/.ant-card-body {
    height: 100%;
    display: flex;
    flex-direction: column;
  }
}
.contain {
  margin-top: 8px;
  border: 1px solid #e8e8e8;
  flex: 1;
  overflow: auto;
  padding: 0 4px;
}
.item {
  cursor: pointer;
  box-shadow: 0px 4px 5px -2px #000;
  padding: 6px 0;
  border-radius: 4px;
  display: flex;
  flex-direction: row;
  align-items: center;
  & + .item {
    margin-top: 4px;
  }
  &:hover {
    transform: scale(0.98, 0.9);
    box-shadow: 0px 2px 5px -2px #000;
    background: #f0f0f0;
  }
  &.selected {
    transform: scale(0.98, 0.9);
    box-shadow: 0px 2px 5px -2px #000;
    color: #13c2c2;
    font-weight: bold;
  }
  .version {
    flex: 1;
  }
  .status {
    display: inline-block;
    width: 10px;
    height: 10px;
    background: #aaa;
    border-radius: 50%;
    margin-left: 10px;
    margin-right: 20px;
    &.actived {
      background: #00ff79;
    }
  }
}
</style>
src/router/config.js
@@ -117,7 +117,16 @@
              path: 'sop-file',
              name: 'SOP',
              component:()=>import('@/pages/resourceManage/sopFile')
            }
            },
            {
              path: 'sop-history',
              name: 'sop历史版本',
              meta: {
                invisible: true,
                highlight: '/resource/sop-history'
              },
              component: () => import('@/pages/resourceManage/sopFile/sop-history'),
            },
          ]
        },
        {