研发图纸文件管理系统-前端项目
he wei
2023-06-25 b06ce1fd9da4791a9481d64198ec1fade1a73203
UA sop
3个文件已添加
5个文件已修改
1017 ■■■■■ 已修改文件
src/components/table/advance/AdvanceTable.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/SearchArea.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/apis.js 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/descRes.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/list.vue 446 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/pop.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/resourceManage/sopFile/rowRes.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/config.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/table/advance/AdvanceTable.vue
@@ -125,9 +125,9 @@
      refresh() {
        this.$emit('refresh', this.conditions)
      },
      onSearchChange(conditions, searchOptions) {
      onSearchChange(conditions, searchOptions, col) {
        this.conditions = conditions
        this.$emit('search', conditions, searchOptions)
        this.$emit('search', conditions, searchOptions, col)
      },
      toggleScreen() {
        if (this.fullScreen) {
src/components/table/advance/SearchArea.vue
@@ -46,7 +46,7 @@
          {{col.title}}:
        </template>
        <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
        <a-select :showSearch="true" :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small" :get-popup-container="() => $refs.selectRoot" style="width: 8em;">
        <a-select :showSearch="true" :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" :mode="col.search.type ? col.search.type : 'default'" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small" :get-popup-container="() => $refs.selectRoot" style="width: 8em;">
        </a-select>
      </div>
      <div v-else :class="['title', {active: col.search.value}]">
@@ -196,7 +196,7 @@
        col.search.backup = backValue
        this.conditions = getConditions(this.searchCols)
        this.searchOptions = getSearchOptions(this.searchCols)
        this.$emit('change', this.conditions, this.searchOptions)
        this.$emit('change', this.conditions, this.searchOptions, col)
      },
      getConditions(columns) {
        const conditions = {}
src/pages/resourceManage/sopFile/apis.js
@@ -0,0 +1,71 @@
import axios from "@/assets/axios";
/**
 * sop 文件解析
 * @param {*} data {multipartFile}
 * @returns
 */
export const fileParse = (data) => {
  return axios({
    method: "POST",
    url: "sop/excelParse",
    headers: {
      "Content-Type": "multipart/form-data"
    },
    data
  })
}
/**
 * sop 上传提交
 * @param {*} data
 * @returns
 */
export const addSop = (data) => {
  return axios({
    method: "POST",
    url: "sop/confirm",
    data
  })
}
/**
 * 列表查询
 * @param {*} params {code, model}
 * @param {*} data [{chileType: ['通用', '基础'], parentType: '组装'}]
 * @returns
 */
export const getList = (params, data) => {
  return axios({
    method: "POST",
    url: "sop/getSopInfo",
    params,
    data
  })
}
/**
 * 查询所有的type1
 * @returns
 */
export const getSopType1 = () => {
  return axios({
    method: "GET",
    url: "sopFileType/type1"
  })
}
/**
 * 查询type1下的所有的type2
 * 参数 type1
 * @returns
 */
export const getSopType2 = (type1) => {
  return axios({
    method: "GET",
    url: "sopFileType/type2",
    params: {
      type1
    }
  })
}
src/pages/resourceManage/sopFile/descRes.vue
New file
@@ -0,0 +1,205 @@
<template>
  <div class="">
    <table class="table">
      <tbody>
        <tr>
          <th class="title" colspan="9">文件基本信息</th>
        </tr>
        <tr>
          <th class="col-1">文件名称</th>
          <td colspan="8">{{ info.fileName }}</td>
        </tr>
        <tr
          v-for="(item, idx) in typeList"
          :key="'li1_' + idx"
          :rowspan="0 == idx ? sumRows : 1"
        >
          <template v-if="0 == idx">
            <th class="col-1" :rowspan="sumRows">文件类型</th>
          </template>
          <td
            v-for="(sub, i) in item"
            :colspan="item.length == 2 ? 4 : 1"
            :key="'li2_' + i"
          >
            <template v-if="item.length != 2 && sub.name">
              <a-icon v-if="sub.checked" type="check-square" />
              <a-icon v-else type="border" />
            </template>
            {{ sub.name }}
          </td>
        </tr>
        <tr>
          <th class="col-1">文件版本</th>
          <td colspan="4">{{ info.fileVersion }}</td>
          <th class="col-1">关联版本</th>
          <td colspan="3">{{ info.fileRelatedVersion }}</td>
        </tr>
        <tr>
          <th class="col-1">编制</th>
          <td colspan="4">{{ info.editor }}</td>
          <th class="col-1">审核</th>
          <td colspan="3">{{ info.auditor }}</td>
        </tr>
        <tr>
          <th class="col-1">发布时间</th>
          <td colspan="8">{{ info.releaseDate }}</td>
        </tr>
        <tr>
          <th class="title" colspan="9">文件适用产品</th>
        </tr>
        <tr v-for="(item, idx) in sopProductList" :key="idx">
          <th class="col-1">物料编码</th>
          <td colspan="4">{{ item.code }}</td>
          <th class="col-1">型/板号</th>
          <td colspan="3">{{ item.model }}</td>
        </tr>
        <tr>
          <th class="col-1">发布说明</th>
          <td colspan="8">{{ info.releaseNotes }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
<script>
export default {
  name: "",
  props: {
    info: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      sumRows: 2,
      typeList: [],
    };
  },
  computed: {
    sopProductList() {
      return this.info.sopProductList || [];
    },
  },
  methods: {
    format() {
      // let list = [
      //   {
      //     name: "组装",
      //     labels: ["a", "b", "c"],
      //   },
      //   {
      //     name: "测试",
      //     labels: ["a", "b", "c", "d", "e"],
      //   },
      //   {
      //     name: "xx",
      //     labels: ["a", "b", "c", "d", "e"],
      //   },
      // ];
      // fileType=组装-线束;组装-复用;组装-test;测试-单板;测试-联调;生产-线束;
      let fileType = this.info.fileType;
      let selected = fileType.split(";").filter((v) => v != "");
      let _types = {};
      selected.forEach((v) => {
        let arr = v.split("-");
        _types[arr[0]] = _types[arr[0]] || [];
        _types[arr[0]].push(arr[1]);
      });
      let list = this.info.fileTypeList.map((v) => {
        let name = Object.keys(v)[0];
        return {
          name,
          labels: v[name].map((item) => {
            let checked = _types[name]
              ? _types[name].some((a) => a == item)
              : false;
            return {
              name: item,
              checked,
            };
          }),
        };
      });
      let resList = [];
      for (let i = 0, j = list.length; i < j; i += 2) {
        // 同一行两个类别占用的最大行数
        let rows = 0;
        let row0 = 0;
        let row1 = 0;
        let item0 = list[i];
        let item1 = list[i + 1];
        // 是否有第二个元素
        let hasItem1 = i + 1 < j;
        // 当前行的第一个类占用的行数
        row0 = Math.ceil(item0.labels.length / 4);
        if (hasItem1) {
          row1 = Math.ceil(item1.labels.length / 4);
        }
        rows = Math.max(row0, row1);
        let trs = [
          [{ name: item0.name }, { name: hasItem1 ? item1.name : "" }],
        ];
        for (let m = 0; m < rows; m++) {
          let idx0 = m * 4;
          let idx1 = m * 4 + 1;
          let idx2 = m * 4 + 2;
          let idx3 = m * 4 + 3;
          let li0 = [];
          let li1 = [];
          li0[0] = item0.labels[idx0] || {};
          li0[1] = item0.labels[idx1] || {};
          li0[2] = item0.labels[idx2] || {};
          li0[3] = item0.labels[idx3] || {};
          if (hasItem1) {
            li1[0] = item1.labels[idx0] || {};
            li1[1] = item1.labels[idx1] || {};
            li1[2] = item1.labels[idx2] || {};
            li1[3] = item1.labels[idx3] || {};
          } else {
            li1 = [{}, {}, {}, {}];
          }
          trs.push([...li0, ...li1]);
        }
        resList.push(...trs);
      }
      this.sumRows = resList.length;
      this.typeList = resList;
    },
  },
  mounted() {
    this.format();
  },
};
</script>
<style lang="less" scoped>
.table {
  width: 100%;
  // table-layout: fixed;
  border-collapse: collapse;
  th,
  td {
    border: 1px #333 solid;
    padding: 4px;
  }
  td {
    // color: #13c2c2;
    color: #333;
  }
  .title {
    font-weight: 900;
    padding-left: 2em;
    font-style: italic;
  }
}
.col-1 {
  // word-break:break-all;
  width: 6.4em;
}
</style>
src/pages/resourceManage/sopFile/list.vue
@@ -4,18 +4,18 @@
      <a-spin class="" :spinning="spinning" tip="拼命加载中...">
        <a-card>
          <advance-table
              ref="table"
              class="doc-center-table"
              :data-source="dataSource"
              :columns="columns"
              title=""
              :row-key="(record, index) => index"
              @search="onSearch"
              @refresh="onRefresh"
              @reset="onReset"
              :format-conditions="true"
              :scroll="{y}"
              :pagination="{
            ref="table"
            class="doc-center-table"
            :data-source="dataSource"
            :columns="columns"
            title=""
            :row-key="(record, index) => index"
            @search="onSearch"
            @refresh="onRefresh"
            @reset="onReset"
            :format-conditions="true"
            :scroll="{ y }"
            :pagination="{
              current: pageCurr,
              pageSize: pageSize,
              total: total,
@@ -27,33 +27,114 @@
                `第 ${range[0]}-${range[1]} 条,总计 ${total} 条`,
              onChange: onPageChange,
              onShowSizeChange: onSizeChange,
            }">
            }"
          >
            <template slot="title">
              <a-space class="operator">
                <span class="title">SOP</span>
                <a-upload
                  v-if="canUpload"
                  :before-upload="beforeUpload"
                  :showUploadList="false"
                  @change="uploadChange"
                  accept=".xls,.xlsx"
                >
                  <a-button type="primary">新增</a-button>
                </a-upload>
              </a-space>
            </template>
            <template slot="action" slot-scope="{ record }">
              <a class="action-button" @click="viewPdf(record)">预览</a>
              <a-popover title="" trigger="hover">
                <div class="" slot="content" style="width: 450px">
                  <a-table
                    size="small"
                    :scroll="{ y: 300 }"
                    bordered
                    :columns="prodsColumns"
                    :data-source="record.sopProductList"
                    :pagination="false"
                    :expandRowByClick="true"
                    :row-key="(record1, index) => index"
                  >
                  </a-table>
                </div>
                <a>适用产品</a>
              </a-popover>
              <a-divider type="vertical"></a-divider>
              <!-- <a class="action-button" @click="viewPdf(record)">预览</a> -->
              <a class="action-button" @click="downloadFile(record)">下载</a>
              <a class="action-button" @click="viewHistory(record)">历史详情</a>
              <!-- <a class="action-button" @click="viewHistory(record)">历史详情</a> -->
            </template>
          </advance-table>
        </a-card>
      </a-spin>
    </div>
    <!-- 弹窗 解析结果 -->
    <a-modal
      :visible="resShow"
      title="解析结果"
      :destroyOnClose="true"
      :maskClosable="false"
      :width="800"
      okText="提交"
      @cancel="resCancel"
      @ok="uploadOk"
    >
      <desc-res :info="resObj"></desc-res>
    </a-modal>
    <pop
      :visible.sync="popVisible"
      :x="popPosition.x"
      :y="popPosition.y"
      :position="popPosition.dir"
      :info="popInfo"
    ></pop>
  </div>
</template>
<script>
import AdvanceTable from "@/components/table/advance/AdvanceTable";
import getWebUrl from "@/assets/js/tools/getWebUrl";
import checkPermit from "@/assets/js/tools/checkPermit";
import PERMITS from "@/assets/js/const/const_permits";
import { mapGetters } from "vuex";
import { fileParse, addSop, getList, getSopType1, getSopType2 } from "./apis";
import offset from "@/assets/js/tools/offset";
import Pop from "./pop";
import DescRes from "./descRes";
export default {
  components: {
    AdvanceTable
    AdvanceTable,
    Pop,
    DescRes,
  },
  name: "list",
  data() {
    return {
      webUrl: getWebUrl(),
      prodsColumns: [
        {
          title: "物料编码",
          dataIndex: "code",
          align: "center",
        },
        {
          title: "型/板号",
          dataIndex: "model",
          align: "center",
          width: 180,
        },
      ],
      resObj: {},
      popInfo: {},
      resShow: false,
      popVisible: false,
      popPosition: {
        x: 500,
        y: 100,
        dir: "bottom",
      },
      spinning: false,
      loading: false,
      pageCurr: 1,
@@ -64,60 +145,121 @@
      conditions: {},
      columns: [
        {
          title: "名称",
          dataIndex: "parentName",
          key: "parentName",
          title: "文件类型",
          dataIndex: "parentType",
          dataType: "select",
          align: "center",
          width: 140,
          visible: false,
          searchAble: true,
          noSearch: true,
          search: {
            selectOptions: [],
          },
        },
        {
          title: "文件子类型",
          dataIndex: "chileType",
          dataType: "select",
          align: "center",
          width: 140,
          visible: false,
          searchAble: true,
          noSearch: true,
          search: {
            type: "multiple",
            value: [],
            selectOptions: [],
          },
        },
        {
          title: "物料编码",
          dataIndex: "code",
          align: "center",
          width: 140,
          searchAble: true,
          noSearch: true,
          visible: false,
        },
        {
          title: "版本",
          dataIndex: "version",
          key: "version",
          title: "型/板号",
          dataIndex: "model",
          align: "center",
          width: 40,
          searchAble: true,
          noSearch: true,
          visible: false,
        },
        {
          title: "文件名称",
          dataIndex: "fileName",
          align: "center",
          width: 140,
          searchAble: false,
          customCell: this.customCell,
        },
        {
          title: "文件版本",
          dataIndex: "fileVersion",
          align: "center",
          width: 80,
          searchAble: false,
          customCell: this.customCell,
        },
        {
          title: "关联版本",
          dataIndex: "fileRelatedVersion",
          align: "center",
          width: 80,
          searchAble: false,
          customCell: this.customCell,
        },
        {
          title: "发布时间",
          dataIndex: "releaseDate",
          align: "center",
          width: 160,
          customCell: this.customCell,
        },
        {
          title: "负责人",
          dataIndex: "owner",
          key: "owner",
          dataIndex: "editor",
          align: "center",
          width: 40,
          searchAble: true,
          width: 90,
          customCell: this.customCell,
        },
        {
          title: "版本说明",
          dataIndex: "ownerExplain",
          key: "ownerExplain",
          title: "审核人",
          dataIndex: "auditor",
          align: "center",
          width: 220,
          width: 90,
          customCell: this.customCell,
        },
        {
          title: "未确认人员",
          dataIndex: "unconfirmed",
          key: "unconfirmed",
          title: "发布说明",
          dataIndex: "releaseNotes",
          align: "center",
          width: 160,
          width: 260,
          customCell: this.customCell,
        },
        {
          title: "操作",
          dataIndex: "operation",
          key: "operation",
          align: "center",
          width: 120,
          width: 140,
          fixed: "right",
          scopedSlots: { customRender: "action" },
          noSearch: true,
        },
      ],
      dataSource: [
        {
          parentName: "新FBI系列流程卡"
        }
      ],
    }
      dataSource: [],
    };
  },
  computed: {
    ...mapGetters("account", ["permits"]),
    canUpload() {
      return checkPermit(PERMITS.uploadSoftware, this.permits);
    },
  },
  watch: {
    update(n) {
@@ -125,7 +267,7 @@
        this.$nextTick(() => {
          const table = this.$refs.table;
          const header = document.querySelectorAll(
              ".doc-center-table .ant-table-header"
            ".doc-center-table .ant-table-header"
          )[0].clientHeight;
          const bar = document.querySelectorAll(".header-bar")[0].clientHeight;
          if (table.fullScreen) {
@@ -153,16 +295,149 @@
    },
  },
  methods: {
    getSopType1() {
      getSopType1()
        .then((res) => {
          let { code, data } = res.data;
          let list = [];
          if (code) {
            list = data.map((v) => ({
              title: v.type1,
              value: v.type1,
            }));
          }
          this.columns[0].search.selectOptions = list;
        })
        .catch((err) => {
          console.log(err);
        });
    },
    getSopType2(type) {
      if (type) {
        getSopType2(type)
          .then((res) => {
            let { code, data } = res.data;
            let list = [];
            if (code) {
              list = data.map((v) => ({
                title: v.type2,
                value: v.type2,
              }));
            }
            this.columns[1].search.selectOptions = list;
          })
          .catch((err) => {
            console.log(err);
          });
      } else {
        this.columns[1].search.selectOptions = [];
      }
    },
    cellMouseenter(e, obj) {
      // console.log("enter", e, obj);
      const wraper = this.$refs.wraper;
      const { clientHeight, clientWidth } = wraper;
      const { target, clientX, clientY } = e;
      let { left: x, top: y } = offset(wraper);
      x = clientX - x;
      y = clientY - y;
      // 如果clientHeight 小于380 * 2 则左右布局
      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 < 400) {
          x = 400;
        }
        if (x + 400 > clientWidth) {
          x = clientWidth - 400;
        }
      }
      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;
    },
    customCell(record) {
      return {
        on: {
          mouseenter: (e) => this.cellMouseenter(e, record),
          mouseleave: (e) => this.cellMouseleave(e, record),
        },
      };
    },
    beforeUpload() {
      return false;
    },
    uploadChange(data) {
      let loading = this.$layer.loading();
      const formData = new FormData();
      formData.append("multipartFile", data.file);
      fileParse(formData)
        .then((res) => {
          this.$layer.close(loading);
          let { code, data, data2 } = res.data;
          if (code && data) {
            this.resObj = data2;
            this.resShow = true;
            this.$message.success("解析成功");
          } else {
            this.$message.error("解析失败");
          }
        })
        .catch((error) => {
          this.$layer.close(loading);
          console.log(error);
        });
    },
    resCancel() {
      this.resShow = false;
    },
    uploadOk() {
      addSop(this.resObj).then((res) => {
        let { code, data, msg } = res.data;
        if (code) {
          this.$message.success("上传成功");
          this.resShow = false;
          this.searchData();
        } else {
          this.$message.error(msg);
        }
      });
    },
    resize() {
      setTimeout(() => {
        this.update = Math.random();
      }, 200);
    },
    onSearch(conditions, searchOptions) {
    onSearch(conditions, searchOptions, col) {
      // console.log(conditions);
      // console.log(searchOptions);
      // console.log(searchOptions, "00", col);
      this.pageCurr = 1;
      this.conditions = conditions;
      if ("parentType" == col.dataIndex) {
        this.parentTypeChange(col.search.value);
      }
      this.searchData();
    },
    onPageChange(page, pageSize) {
@@ -183,21 +458,47 @@
      this.conditions = conditions;
      this.searchData();
    },
    // 文件类型查询条件发生变化  查询子文件类型列表 重置子文件类型值
    parentTypeChange(val) {
      let _search = this.columns[1].search;
      _search.value = [];
      this.conditions['chileType'] = [];
      this.getSopType2(val);
    },
    searchData() {
      const { pageCurr, pageSize, conditions, columns } = this;
      let params = {};
      let data = {};
      Object.keys(conditions).forEach((v) => {
        switch (v) {
          case "code":
          case "model":
            params[v] = conditions[v];
            break;
          case "parentType":
          case "chileType":
            data[v] = conditions[v];
            break;
        }
      });
      let list = [];
      for(let i=0; i<100; i++) {
        list.push({
          parentName: "新FBI系列流程卡"+i,
          version: "V"+i,
          owner: "霍东伟",
          ownerExplain: "流程卡测试说明",
          unconfirmed: "霍东伟,鲁星伟,李军"
      let params2 = "{}" == JSON.stringify(data) ? [] : [data];
      getList(params, params2)
        .then((res) => {
          let { code, data, data2 } = res.data;
          if (code && data) {
            // console.log(data2);
            list = data2;
          }
          this.dataSource = list;
          this.getSopType1();
          if (-1 == this.update) {
            this.update = Math.random();
          }
        })
        .catch((err) => {
          console.log(err);
        });
      }
      this.dataSource = list;
      if (-1 == this.update) {
        this.update = Math.random();
      }
    },
    viewPdf(record) {
      console.log(record);
@@ -206,18 +507,25 @@
      console.log(record);
    },
    downloadFile(record) {
      console.log(record);
    }
  },
  computed: {
      // console.log(record);
      let loading = this.$layer.loading();
      let link = document.createElement("a");
      link.style.display = "none";
      let url = this.webUrl + record.fileUrl;
      link.href = url;
      link.download = url;
      document.body.appendChild(link);
      link.click();
      this.$layer.close(loading);
      document.body.removeChild(link);
    },
  },
  mounted() {
    this.searchData();
    this.getSopType1();
    window.addEventListener("resize", this.resize);
  }
}
  },
};
</script>
<style scoped lang="less">
@@ -249,4 +557,12 @@
/deep/table {
  table-layout: fixed;
}
/deep/ .ant-table-fixed-right {
  .ant-table-body-outer {
    margin-bottom: 0 !important;
  }
  .ant-table-body-inner {
   overflow-x: hidden;
 }
}
</style>
src/pages/resourceManage/sopFile/pop.vue
New file
@@ -0,0 +1,176 @@
<template>
  <div
    ref="pop"
    :class="['pop', position]"
    :style="{
      left: x + 'px',
      top: y + 'px',
      display: visible ? 'block' : 'none',
    }"
  >
    <div class="inner">
      <desc-res :info="info"></desc-res>
    </div>
  </div>
</template>
<script>
import DescRes from "./rowRes";
export default {
  name: "",
  components: {
    DescRes,
  },
  props: {
    info: {
      type: Object,
      default() {
        return {};
      },
    },
    x: {
      type: Number,
      default: 0,
    },
    y: {
      type: Number,
      default: 0,
    },
    visible: {
      type: Boolean,
      default: false,
    },
    position: {
      type: String,
      default: "bottom",
    },
  },
  data() {
    return {};
  },
  methods: {
    onMouseenter() {
      this.$emit('update:visible', true);
    },
    onMouseleave() {
      this.$emit('update:visible', false);
    }
  },
  mounted() {
    this.$refs.pop.addEventListener('mouseenter', this.onMouseenter);
    this.$refs.pop.addEventListener('mouseleave', this.onMouseleave);
  },
  beforeDestroy() {
    this.$refs.pop.removeEventListener('mouseenter', this.onMouseenter);
    this.$refs.pop.removeEventListener('mouseleave', this.onMouseleave);
  }
};
</script>
<style lang="less" scoped>
.pop {
  position: absolute;
  z-index: 1;
  background: rgba(0, 0, 0, 0.6);
  width: 800px;
  padding: 10px;
  border-radius: 6px;
  /deep/ th,
  /deep/ td {
    border: 1px #fff solid;
    color: #fff;
  }
  &.top {
    transform: translate(-50%, -100%);
    margin-top: -10px;
    &::after {
      content: "";
      position: absolute;
      top: 100%;
      left: 50%;
      transform: translateX(-5px);
      display: inline-block;
      width: 0;
      height: 0;
      border-top: 10px solid rgba(0, 0, 0, 0.6);
      border-left: 5px solid transparent;
      border-right: 5px solid transparent;
    }
    &::before {
      content: "";
      position: absolute;
      top: 100%;
      left: 0;
      right: 0;
      display: inline-block;
      height: 10px;
      background: transparent;
    }
  }
  &.bottom {
    margin-top: 10px;
    transform: translate(-50%, 0%);
    &::after {
      content: "";
      position: absolute;
      bottom: 100%;
      left: 50%;
      transform: translateX(-5px);
      display: inline-block;
      width: 0;
      height: 0;
      border-bottom: 10px solid rgba(0, 0, 0, 0.6);
      border-left: 5px solid transparent;
      border-right: 5px solid transparent;
    }
    &::before {
      content: "";
      position: absolute;
      bottom: 100%;
      left: 0;
      right: 0;
      height: 10px;
      background: transparent;
    }
  }
  &.left {
    margin-left: -10px;
    transform: translate(-100%, -50%);
    &::after {
      content: "";
      position: absolute;
      left: 100%;
      top: 50%;
      transform: translateY(-5px);
      display: inline-block;
      width: 0;
      height: 0;
      border-left: 10px solid rgba(0, 0, 0, 0.6);
      border-top: 5px solid transparent;
      border-bottom: 5px solid transparent;
    }
  }
  &.right {
    margin-left: 10px;
    transform: translate(0%, -50%);
    &::after {
      content: "";
      position: absolute;
      right: 100%;
      top: 50%;
      transform: translateY(-5px);
      display: inline-block;
      width: 0;
      height: 0;
      border-right: 10px solid rgba(0, 0, 0, 0.6);
      border-top: 5px solid transparent;
      border-bottom: 5px solid transparent;
    }
  }
  .inner {
    max-height: 360px;
    overflow-y: auto;
  }
}
</style>
src/pages/resourceManage/sopFile/rowRes.vue
New file
@@ -0,0 +1,101 @@
<template>
  <div class="">
    <table class="table">
      <tbody>
        <tr>
          <th class="title" colspan="9">文件基本信息</th>
        </tr>
        <tr>
          <th class="col-1">文件名称</th>
          <td colspan="8">{{ info.fileName }}</td>
        </tr>
        <tr>
          <th class="col-1">文件类型</th>
          <td colspan="8">{{ info.fileType }}</td>
        </tr>
        <tr>
          <th class="col-1">文件版本</th>
          <td colspan="4">{{ info.fileVersion }}</td>
          <th class="col-1">关联版本</th>
          <td colspan="3">{{ info.fileRelatedVersion }}</td>
        </tr>
        <tr>
          <th class="col-1">编制</th>
          <td colspan="4">{{ info.editor }}</td>
          <th class="col-1">审核</th>
          <td colspan="3">{{ info.auditor }}</td>
        </tr>
        <tr>
          <th class="col-1">发布时间</th>
          <td colspan="8">{{ info.releaseDate }}</td>
        </tr>
        <tr>
          <th class="title" colspan="9">文件适用产品</th>
        </tr>
        <tr v-for="(item, idx) in sopProductList" :key="idx">
          <th class="col-1">物料编码</th>
          <td colspan="4">{{ item.code }}</td>
          <th class="col-1">型/板号</th>
          <td colspan="3">{{ item.model }}</td>
        </tr>
        <tr>
          <th class="col-1">发布说明</th>
          <td colspan="8">{{ info.releaseNotes }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
<script>
export default {
  name: "",
  props: {
    info: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
    };
  },
  computed: {
    sopProductList() {
      return this.info.sopProductList || [];
    },
  },
  methods: {
  },
  mounted() {
  },
};
</script>
<style lang="less" scoped>
.table {
  width: 100%;
  // table-layout: fixed;
  border-collapse: collapse;
  th,
  td {
    border: 1px #fff solid;
    padding: 4px;
  }
  td {
    color: #fff;
  }
  .title {
    font-weight: 900;
    padding-left: 2em;
    font-style: italic;
  }
}
.col-1 {
  // word-break:break-all;
  width: 6.4em;
}
</style>
src/router/config.js
@@ -113,11 +113,11 @@
            //   name: '流程卡',
            //   component:()=>import('@/pages/resourceManage/flowCard')
            // },
            // {
            //   path: 'sop-file',
            //   name: 'SOP',
            //   component:()=>import('@/pages/resourceManage/sopFile')
            // }
            {
              path: 'sop-file',
              name: 'SOP',
              component:()=>import('@/pages/resourceManage/sopFile')
            }
          ]
        },
        {