祖安之光
2025-07-16 f96b425b531a70c541b63b66f7b18c1d1b87fb1d
src/views/build/conpanyFunctionConsult/qualityManage/rangeManage/range/index.vue
@@ -9,7 +9,6 @@
              remote
              @change="selectValue"
              reserve-keyword
              placeholder="请输入企业名称"
              remote-show-suffix
              :remote-method="getCompanyList"
              :loading="loadingCompany"
@@ -23,71 +22,116 @@
            />
          </el-select>
        </el-form-item>
        <el-form-item v-if="data.isAdmin">
          <el-button type="primary" style="margin-left: 30px" @click="searchClick">查询</el-button>
          <el-button plain @click="reset">重置</el-button>
        </el-form-item>
<!--        <el-form-item v-if="data.isAdmin">-->
<!--          <el-button type="primary" style="margin-left: 30px" @click="searchClick">查询</el-button>-->
<!--          <el-button plain @click="reset">重置</el-button>-->
<!--        </el-form-item>-->
      </el-form>
    </div>
    <div class="bottom">
      <div class="left">
        <span style="font-weight: 600;font-size: 24px">目录</span>
        <el-tree
            style="max-width: 600px;margin-top: 20px"
            :data="data.treeData"
            :props="data.defaultProps"
            :default-expand-all="true"
            :expand-on-click-node="false"
            @node-click="handleNodeClick"
        />
        <div  class="tree-container tree-hide-scrollbar"  v-if="data.treeData">
          <el-tree
              ref="treeRef"
              :data="data.treeData"
              :props="data.defaultProps"
              :default-expand-all="true"
              :expand-on-click-node="false"
              highlight-current
              node-key="id"
              :current-node-key="currentSelectedKey"
              @node-click="handleNodeClick"
          >
            <template #default="{ node,data }">
              <el-tooltip
                  :content="data.mess"
                  placement="bottom"
                  :disabled="!isTextOverflow(data)"
              >
                <span class="tree-text">{{ data.mess }}</span>
              </el-tooltip>
            </template>
          </el-tree>
        </div>
        <el-empty v-else description="暂无数据" />
      </div>
      <div class="right">
        <el-form :model="data.form" size="default" ref="noticeRef" :rules="data.formRules" label-position="left" label-width="125px" >
          <el-form-item label="具体内容" prop="content" >
            <el-input v-model="data.form.content" :rows="4" type="textarea" />
        <el-form :model="state.form" size="default" ref="noticeRef" :rules="data.formRules" label-position="left" label-width="125px" >
          <el-form-item label="具体内容" prop="content" v-if="data.isAdmin">
            <t-editor  style="width: 100%;" :height="400" ref="myEditor1" :value="state.form.content" ></t-editor>
          </el-form-item>
          <el-form-item label="标准分析" prop="analysis" >
            <el-input v-model="data.form.analysis" :rows="4" type="textarea" />
          <el-form-item label="具体内容" prop="content" v-else>
            <div class="ql-container ql-snow" style="height: 500px;width: 100%;margin-top: 10px;" >
              <div class="ql-editor">
                <div v-if="state.form.content" class="reviewTable" v-html="state.form.content"  @click="showFile($event)"></div>
                <el-empty v-else description="暂无数据" />
              </div>
            </div>
          </el-form-item>
          <el-form-item label="标准分析" prop="analysis" v-if="data.isAdmin">
            <t-editor style="width: 100%;" :height="400" ref="myEditor2" :value="state.form.analysis" ></t-editor>
          </el-form-item>
          <el-form-item label="标准分析" prop="content" v-else>
            <div class="ql-container ql-snow" style="height: 500px;width: 100%;margin-top: 10px;" >
              <div class="ql-editor">
                <div v-if="state.form.analysis" class="reviewTable" v-html="state.form.analysis"  @click="showFile($event)"></div>
                <el-empty v-else description="暂无数据" />
              </div>
            </div>
          </el-form-item>
          <el-form-item label="应准备材料" prop="dataList">
            <el-button type="primary" @click="openDataDialog('add',{})">新增</el-button>
            <el-button v-if="data.isAdmin" type="primary" @click="openDataDialog('add',{})">新增</el-button>
          </el-form-item>
          <el-table style="margin:15px 0;width: 80%" :data="data.form.dataList" :border="true" >
          <el-table style="margin:15px 0;width: 100%" :data="state.form.dataList" :border="true" >
            <el-table-column type="index" label="序号" width="80" align="center"></el-table-column>
            <el-table-column label="材料名称" prop="companyName" align="center"  />
            <el-table-column label="材料模板" prop="companyName" align="center"  />
            <el-table-column label="材料名称" prop="name" align="center"  />
            <el-table-column label="材料模板" prop="fileName" align="center" >
              <template #default="scope">
                <el-link v-if="scope.row.fileName" style="" type="primary" @click="openFile(scope.row.filePath)">{{scope.row.fileName}}</el-link>
              </template>
            </el-table-column>
            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" >
              <template #default="scope">
                <el-button link type="primary"  @click="openDataDialog('edit',scope.row)" >编辑</el-button>
                <el-button link type="danger"  @click="handleDataDelete(scope.row)" >删除</el-button>
                <el-button v-if="scope.row.filePath" link type="primary"  @click="downloadFile(scope.row)" >下载</el-button>
                <el-button v-if="data.isAdmin" link type="primary"  @click="openDataDialog('edit',scope.row)" >编辑</el-button>
                <el-button v-if="data.isAdmin" link type="danger"  @click="handleDataDelete(scope.row)" >删除</el-button>
              </template>
            </el-table-column>
          </el-table>
          <el-form-item label="记录上传" prop="recordList">
            <el-button type="primary" @click="openRecordDialog('add',{})">新增</el-button>
          </el-form-item>
          <el-table style="margin-top: 15px;width: 80%" :data="data.form.recordList" :border="true" >
            <el-table-column type="index" label="序号" width="80" align="center"></el-table-column>
            <el-table-column label="记录名称" prop="companyName" align="center"  />
            <el-table-column label="记录模板" prop="companyName" align="center"  />
            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" >
              <template #default="scope">
                <el-button link type="primary"  @click="openRecordDialog('edit',scope.row)" >编辑</el-button>
                <el-button link type="danger"  @click="handleRecordDelete(scope.row)" >删除</el-button>
              </template>
            </el-table-column>
          </el-table>
<!--          <el-form-item label="记录上传" prop="recordList">-->
<!--            <el-button type="primary" @click="openRecordDialog('add',{})">新增</el-button>-->
<!--          </el-form-item>-->
<!--          <el-table style="margin-top: 15px;width: 100%" :data="state.form.recordList" :border="true" >-->
<!--            <el-table-column type="index" label="序号" width="80" align="center"></el-table-column>-->
<!--            <el-table-column label="记录名称" prop="name" align="center"  />-->
<!--            <el-table-column label="材料模板" prop="fileName" align="center">-->
<!--              <template #default="scope">-->
<!--                <el-link v-if="scope.row.fileName" style="" type="primary" @click="downloadFile(scope.row)">{{scope.row.fileName}}</el-link>-->
<!--              </template>-->
<!--            </el-table-column>-->
<!--            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" >-->
<!--              <template #default="scope">-->
<!--                <el-button link type="primary"  @click="openRecordDialog('edit',scope.row)" >编辑</el-button>-->
<!--                <el-button link type="danger"  @click="handleRecordDelete(scope.row)" >删除</el-button>-->
<!--              </template>-->
<!--            </el-table-column>-->
<!--          </el-table>-->
        </el-form>
        <el-button type="primary" style="margin-top: 20px;text-align: right;float: right">保存</el-button>
        <div style="display: flex;align-items: center;justify-content: right;margin-top: 10px">
          <el-button v-if="state.form.id && data.isAdmin" type="danger"  @click="deleteData">删除</el-button>
          <el-button v-if="data.isAdmin" type="primary"  @click="addData()">保存</el-button>
        </div>
      </div>
    </div>
    <dataDialog ref="dialogRef" @getList="getList"></dataDialog>
    <record-dialog ref="dialogRecordRef" @getList="getList"></record-dialog>
    <dataDialog ref="dialogRef" @getList="getFileList"></dataDialog>
<!--    <record-dialog ref="dialogRecordRef" @getList="getFileList"></record-dialog>-->
  </div>
</template>
<script setup>
import {getCurrentInstance, onMounted, reactive, ref, toRefs} from "vue";
import {getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs} from "vue";
import Cookies from "js-cookie";
import {ElMessage, ElMessageBox} from "element-plus";
import {getCompany} from "@/api/onlineEducation/company";
@@ -96,71 +140,48 @@
import dataDialog from "./components/dataDialog.vue"
import recordDialog from "./components/recordDialog.vue"
import {delSysClause} from "@/api/staffManage/staff";
import {getCatalogue} from "@/api/qualityManage/catalog";
import {
  addCatalogueData,
  addFile, delCatalogueData,
  delFile,
  editCatalogueData,
  getCatalogueData,
  getFile
} from "@/api/qualityManage/range";
import axios from "axios";
import TEditor from "@/components/Tinymce/Tinymce.vue";
import {renderAsync} from "docx-preview";
const { proxy } = getCurrentInstance();
const loading = ref(false);
const noticeRef = ref();
const dialogRef = ref();
const myEditor1 = ref();
const myEditor2 = ref();
const treeRef = ref()
const dialogRecordRef = ref();
const currentSelectedKey = ref()
const loadingCompany = ref(false)
const choosedData = ref([])
const data = reactive({
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    type: 1,
    companyId: null,
  },
  form: {
    id: '',
    companyId: null,
    content: '',
    analysis: '',
    dataList: [],
    recordList: []
  },
  formRules: {
    content: [{ required: true, message: '请输入具体内容', trigger: 'blur' }],
    analysis: [{ required: true, message: '请输入标准分析', trigger: 'blur' }],
    dataList: [{ required: true, message: '请输入标准分析', trigger: 'blur' }],
    recordList: [{ required: true, message: '请输入标准分析', trigger: 'blur' }]
    // dataList: [{ required: true, message: '', trigger: 'blur' }],
    // recordList: [{ required: true, message: '', trigger: 'blur' }]
  },
  companyList: [],
  isAdmin: false,
  defaultProps: {
    children: 'children',
    label: 'label',
    label: 'name',
  },
  treeData: [
    {
      label: '1范围',
      children: [
        {
          label: '1.1规范范围管理',
          children: [
            {
              label: 'Level three 1-1-1',
              children: [
                {
                  label: 'Level three 1-1-1',
                  children: [
                    {
                      label: 'Level three 1-1-1',
                    },
                  ],
                },
              ],
            },
          ],
        },
        {
          label: '1.2收集需求',
        },
        {
          label: '1.3定义范围',
        },
      ],
    },
  ],
  treeData: [],
  dataVisible: false,
  dataTitle: '',
  uploadUrl: import.meta.env.VITE_APP_BASE_API + '/system/common/uploadFile',
@@ -171,6 +192,17 @@
  fileDataList: []
});
const state = reactive({
  form: {
    id: '',
    companyId: null,
    content: '',
    analysis: '',
    catalogueId: null,
    dataList: [],
    recordList: []
  },
})
const dataList = ref([]);
const total = ref(0);
@@ -184,23 +216,71 @@
    await getCompanyList()
    data.queryParams.companyId = data.companyList[0].id
    data.queryParams.companyName = data.companyList[0].name
    data.form.companyId = data.companyList[0].id
    state.form.companyId = data.companyList[0].id
  }else {
    data.queryParams.companyId = userInfo.companyId
    state.form.companyId = userInfo.companyId
  }
  await getList();
  if(data.treeData.length >0){
    state.form.catalogueId = data.treeData[0].id
    currentSelectedKey.value = state.form.catalogueId
    await getCatalogDataList()
    await getFileList()
  }
})
const getList = async () => {
  data.treeData = [];
  loading.value = true;
  // const res = await getBasic(data.queryParams);
  // if(res.code === 200){
  //   dataList.value = res.data.list
  //   total.value = res.data.total
  // }else{
  //   ElMessage.warning(res.message)
  // }
  const param = {
    type : 1
  }
  const res = await getCatalogue(param);
  if(res.code === 200){
    const menu = await handleTree(res.data.data)
    data.treeData = menu;
  }else{
    ElMessage.warning(res.message)
  }
  loading.value = false;
}
const textMeasureRef = ref(null);
const getMeasureElement = () => {
  if (!textMeasureRef.value) {
    const el = document.createElement('span');
    el.className = 'text-measure-element';
    el.style.cssText = `
      position: absolute;
      visibility: hidden;
      white-space: nowrap;
      font-size: 14px; /* 匹配实际字体大小 */
    `;
    document.body.appendChild(el);
    textMeasureRef.value = el;
  }
  return textMeasureRef.value;
};
// 判断文本是否溢出
const isTextOverflow = (text) => {
  const measureEl = getMeasureElement();
  measureEl.textContent = text.mess;
  return measureEl.scrollWidth > 300; // 180px 是节点容器实际宽度
};
const handleTree = (val) => {
  const traverse = (nodes, currentPath = '') => {
    nodes.forEach((node, index) => {
      node.mess= `${node.number} ${node.mess}`;
      // 递归处理子节点(传递当前序号路径)
      if (node.children && node.children.length) {
        traverse(node.children, node.number);
      }
    });
  };
  traverse(val); // 从根节点开始遍历
  return val;
}
const getCompanyList = async (val)=>{
  if(val){
@@ -219,7 +299,7 @@
  }else {
    loadingCompany.value = true;
    const queryParams = {
      pageSize: 10,
      pageSize: 100,
      pageNum: 1,
    }
    const res = await getCompany(queryParams)
@@ -232,35 +312,124 @@
    }
  }
}
const selectValue = (val) => {
const selectValue = async (val) => {
  state.form.analysis = ''
  state.form.content = ''
  if(data.isAdmin){
    myEditor1.value.myValue = ''
    myEditor2.value.myValue = ''
  }
  state.form.id = ''
  data.companyList.forEach(item => {
    if(item.name === val){
      data.queryParams.companyId = item.id
      state.form.companyId = item.id
    }
  })
}
const searchClick = () => {
  getList();
}
function reset() {
  data.queryParams = {
    companyId: '',
    pageNum: 1,
    pageSize: 10,
  if(state.form.catalogueId){
    await getCatalogDataList()
    await getFileList()
  }
  choosedData.value = []
  data.companyList = [];
  getList();
  getCompanyList()
}
const handleNodeClick = (val) => {
  console.log('node',val)
const handleNodeClick = async (val) => {
  state.form.analysis = ''
  state.form.content = ''
  if(data.isAdmin){
    myEditor1.value.myValue = ''
    myEditor2.value.myValue = ''
  }
  state.form.id = ''
  state.form.catalogueId = val.id
  currentSelectedKey.value = val.id
  await getCatalogDataList()
  await getFileList()
}
const getCatalogDataList = async () => {
  state.form.analysis = ''
  state.form.content = ''
  state.form.id = ''
  const param = {
    catalogueId: state.form.catalogueId,
    companyId: state.form.companyId
  }
  const res = await getCatalogueData(param);
  if(res.code === 200){
    // if(res.data.data && res.data.data.length >0){
      state.form.content= res.data.data[0]?.content
      state.form.analysis = res.data.data[0]?.analysis
      state.form.id = res.data.data[0]?.id
    // }
  }else{
    ElMessage.warning(res.message)
  }
}
const addData = async () => {
  state.form.content = myEditor1.value.myValue
  state.form.analysis = myEditor2.value.myValue
  if(!state.form.catalogueId){
    ElMessage.warning('请先选择左侧目录!')
    return
  }
  // if(state.form.dataList && state.form.dataList.length==0){
  //   ElMessage.warning('请上传应准备材料!')
  //   return
  // }
  // if(state.form.recordList && state.form.recordList.length==0){
  //   ElMessage.warning('请上传记录!')
  //   return
  // }
  const valid = await noticeRef.value.validate();
  if(valid){
    if(state.form.id){
      //编辑
      const {dataList,recordList,...data} = state.form
      const res = await editCatalogueData(data)
      if(res.code == 200){
        ElMessage.success('编辑成功')
        await getCatalogDataList()
        await getFileList()
      }else{
        ElMessage.warning(res.message)
      }
    }else {
      //新增
      const {id,dataList,recordList,...data} = state.form
      const res = await addCatalogueData(data)
      if(res.code == 200){
        ElMessage.success('新增成功')
        await getCatalogDataList()
        await getFileList()
      }else{
        ElMessage.warning(res.message)
      }
    }
  }
}
const openDataDialog = (type, value) => {
  dialogRef.value.openDialog(type, value, data.queryParams.companyId, data.isAdmin, data.companyList);
  if(!state.form.catalogueId){
    ElMessage.warning('请先选择左侧目录!')
    return
  }
  dialogRef.value.openDialog(type, value, state.form.companyId,state.form.catalogueId);
}
const openRecordDialog = (type, value) => {
  dialogRecordRef.value.openDialog(type, value, data.queryParams.companyId, data.isAdmin, data.companyList);
  if(!state.form.catalogueId){
    ElMessage.warning('请先选择左侧目录!')
    return
  }
  dialogRecordRef.value.openDialog(type, value, state.form.companyId,state.form.catalogueId);
}
const handleDataDelete = (val) => {
@@ -273,10 +442,10 @@
        type: 'warning',
      })
      .then( async() => {
        const res = await delSysClause({id: val.id})
        const res = await delFile(val.id)
        if(res.code == 200){
          ElMessage.success('数据删除成功')
          await getList()
          await getFileList()
        }else{
          ElMessage.warning(res.message)
        }
@@ -293,14 +462,153 @@
        type: 'warning',
      })
      .then( async() => {
        const res = await delSysClause({id: val.id})
        const res = await delFile(val.id)
        if(res.code == 200){
          ElMessage.success('数据删除成功')
          await getList()
          await getFileList()
        }else{
          ElMessage.warning(res.message)
        }
      })
}
const deleteData = () => {
  ElMessageBox.confirm(
      '确定删除此条数据?',
      '提示',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
      .then( async() => {
        const res = await delCatalogueData(state.form.id)
        if(res.code == 200){
          ElMessage.success('数据删除成功')
          await getCatalogDataList()
          await getFileList()
        }else{
          ElMessage.warning(res.message)
        }
      })
}
const reset = async () => {
  data.queryParams = {
    companyId: '',
    pageNum: 1,
    pageSize: 10,
  }
  data.companyList = [];
  await getList();
  await  getCompanyList()
  data.queryParams.companyId = data.companyList[0].id
  data.queryParams.companyName = data.companyList[0].name
  state.form.companyId = data.companyList[0].id
}
const getFileList = async () => {
  const queryParams = {
    companyId: state.form.companyId,
    catalogueId: state.form.catalogueId
  }
  const res = await getFile(queryParams)
  if (res.code == 200) {
    state.form.dataList = res.data.data?.filter(i => i.type == 1)
    state.form.recordList = res.data.data?.filter(i => i.type == 2)
  } else {
    ElMessage.warning(res.message)
  }
}
const openFile = async(path)=>{
  const ext = path.split('.').pop().toLowerCase();
  if (ext === 'doc') {
    ElMessageBox.confirm('暂不支持线上预览.doc文件,是否下载查看?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }).then(() => {
      window.open(`${import.meta.env.VITE_APP_BASE_API}/${path}`, '_blank');
    }).catch(() => {
      console.log('取消预览')
    });
    return
  }
  try {
    // 1. 获取文件
    const response = await fetch(import.meta.env.VITE_APP_BASE_API + '/' + path);
    const arrayBuffer = await response.arrayBuffer();
    // 2. 创建新窗口
    const win = window.open('', '_blank')
    win.document.write(`
      <!DOCTYPE html>
      <html>
        <head>
          <title>预览</title>
          <style>
            body { margin: 20px; font-family: Arial; }
            .docx-container { width: 100%; height: 100%; }
          </style>
        </head>
        <body>
          <div id="container" class="docx-container"></div>
        </body>
      </html>
    `);
    // 3. 渲染 DOCX
    await renderAsync(arrayBuffer, win.document.getElementById('container'));
  } catch (error) {
    console.error('预览失败:', error);
    alert(`预览失败: ${error.message}`);
  }
}
const downloadFile = (e)=>{
  axios.get(import.meta.env.VITE_APP_BASE_API + '/' +e.filePath,{headers:{'Content-Type': 'application/json','Authorization': `${getToken()}`},responseType: 'blob'}).then(res=>{
    if (res) {
      const link = document.createElement('a')
      let blob = new Blob([res.data],{type: res.data.type})
      link.style.display = "none";
      link.href = URL.createObjectURL(blob); // 创建URL
      link.setAttribute("download", e.fileName);
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    } else {
      ElMessage({
        type: 'warning',
        message: '文件读取失败'
      });
    }
  })
}
const showFile = (e) => {
  if(e.target.nodeName === 'A'){
    console.log("e",e)
    e.preventDefault();
    const file = {
      fileUrl: e.target.href,
      fileName: e.target.innerHTML
    }
    axios.get( file.fileUrl,{
          headers:
              {
                'Content-Type': 'application/json',
                'Authorization':getToken(),
              },
          responseType: 'blob'
        }
    ).then(res=>{
      if (res) {
        const link = document.createElement('a')
        let blob = new Blob([res.data],{type: res.data.type})
        link.style.display = "none";
        link.href = URL.createObjectURL(blob); // 创建URL
        link.setAttribute("download", file.fileName);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      } else {
        this.$message.error('获取文件失败')
      }
      // handleClose();
    })
  }
}
</script>
@@ -314,7 +622,7 @@
    margin: 20px;
    .left{
      border-right: 1px solid darkgray;
      //border-right: 1px solid darkgray;
      display: flex;
      min-width: 240px;
      margin: 20px 20px 20px 50px;
@@ -322,14 +630,80 @@
      :deep(.el-tree){
        background: none;
      }
      :deep(.el-tooltip )
        {
          color: black;
          text-overflow: ellipsis;
          overflow: hidden;
          word-break: break-all;
          white-space: nowrap;
        }
      .tree-text {
        display: inline-block;
        max-width: 300px;   /* 根据实际容器宽度调整 */
        white-space: nowrap; /* 强制不换行 */
        overflow: hidden;    /* 隐藏溢出 */
        text-overflow: ellipsis; /* 显示省略号 */
      }
      /* 可选:移除el-tree默认的节点内边距 */
      .el-tree-node__content {
        padding-right: 5px;
      }
      .tree-container {
        max-width: 600px;
        margin-top: 20px;
        height: 100%;
        max-height: 1200px;
        box-shadow: 8px 0 15px rgba(0,21,41,0.08);
        overflow: auto;  /* 确保出现滚动条 */
      }
      /* 隐藏默认滚动条 */
      .tree-hide-scrollbar::-webkit-scrollbar {
        width: 5px;
        background-color: transparent;
      }
      .tree-hide-scrollbar::-webkit-scrollbar-thumb {
        background-color: transparent;
        border-radius: 4px;
      }
      /* 鼠标悬停时显示滚动条 */
      .tree-hide-scrollbar:hover::-webkit-scrollbar-thumb {
        background-color: #e1e1e1;
      }
      .tree-hide-scrollbar:hover::-webkit-scrollbar-track {
        background-color: #f5f7fa;
      }
      .tree{
        max-width: 600px;
        margin-top: 20px;
        height: 800px;
        overflow-x: hidden;
        box-shadow: 8px 0 15px rgba(0,21,41,0.08)
      }
    }
    .right{
      margin: 20px 20px 20px 10px;
      flex: 1;
      min-width: 100px;
      :deep(.el-form-item__label){
        font-weight: 600;font-size: 20px
      }
      .reviewTable {
        :deep(table){
          border: 1px solid #ccc;
          text-align: center;
        }
        :deep(table td){
          border: 1px solid #ccc;
          text-align: center;
          padding: 0 5px;
        }
        :deep(table th){
          border: 1px solid #ccc;
        }
      }
    }
  }