<template>
|
<div class="app-container">
|
<div style="display: flex;justify-content: space-between">
|
<el-form :inline="true" style="display: flex;align-items: center;flex-wrap: wrap;" >
|
<!-- <el-form-item>-->
|
<!-- <el-button-->
|
<!-- type="primary"-->
|
<!-- plain-->
|
<!-- icon="Plus"-->
|
<!-- @click="openDialog('add',{})"-->
|
<!-- >新增</el-button>-->
|
<!-- </el-form-item>-->
|
<el-form-item v-if="isAdmin" label="企业:" >
|
<el-select v-model="data.queryParams.companyId" placeholder="请选择" clearable>
|
<el-option
|
v-for="item in companyList"
|
:key="item.id"
|
:label="item.name"
|
:value="item.id">
|
</el-option>
|
</el-select>
|
</el-form-item>
|
<el-form-item >
|
<el-button v-if="isAdmin" type="primary" @click="getList">查询</el-button>
|
<el-button v-if="isAdmin" type="primary" plain @click="reset">重置</el-button>
|
<el-button type="primary" @click="addFile">生成质量手册</el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
<!-- 表格数据 -->
|
<el-table v-loading="loading" :data="dataList" :border="true">
|
<el-table-column label="序号" type="index" align="center" width="80"/>
|
<el-table-column label="质量手册" prop="fileName" align="center">
|
<template #default="scope">
|
<el-button link type="primary" @click="initFile(scope.row)">{{scope.row.companyName + scope.row.qualityName + '.docx'}}</el-button>
|
</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="initFile(scope.row)">下载</el-button>
|
<el-button link type="primary" @click="openDialog('add',scope.row)">上传</el-button>
|
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<div class="orgTreeBox" id="org-tree-container">
|
<vue3-tree-org
|
:data="deptList"
|
:horizontal="false"
|
:props="treeProps"
|
:toolBar="tools"
|
:label-style="labelStyle"
|
center
|
/>
|
</div>
|
<pagination
|
v-show="total > 0"
|
:total="total"
|
v-model:page="queryParams.pageNum"
|
v-model:limit="queryParams.pageSize"
|
@pagination="getList"
|
/>
|
<edit-dialog ref="dialogRef" @getList=getList></edit-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import {getCurrentInstance, onMounted, onUnmounted, reactive, ref, toRefs} from "vue";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
import {delCompany, getCompany} from "@/api/onlineEducation/company";
|
import {generateWordDocument} from './components/exportDoc.js'
|
import editDialog from './components/editDialog.vue'
|
import html2canvas from 'html2canvas'
|
import { saveAs } from 'file-saver'
|
import useUserStore from "@/store/modules/user";
|
import {
|
addStandardQuality,
|
delStandardQuality,
|
getStandardDetail,
|
getStandardQuality
|
} from "@/api/standardSys/standardSys";
|
import {getDepart, getDistribution, getSysClause} from "@/api/orgStructure/depart";
|
|
const userStore = useUserStore()
|
const { proxy } = getCurrentInstance();
|
const loading = ref(false);
|
const dialogRef = ref();
|
const data = reactive({
|
queryParams: {
|
pageNum: 1,
|
pageSize: 10,
|
companyId: null
|
},
|
total: 0,
|
dataList: [],
|
deptList: {
|
id: 0,
|
deptName: "",
|
children:[]
|
},
|
treeProps: {
|
label: 'deptName'
|
},
|
tools: {
|
scale: false, restore: false, expand: false, zoom: false, fullscreen: false
|
},
|
labelStyle: {
|
border: '1px solid #ccc',
|
background: 'rgba(0,0,0,0)'
|
},
|
companyList: [],
|
isAdmin: false,
|
companyInfo: {},
|
caluseList: [],
|
originDeptList: []
|
});
|
|
const { queryParams, total, dataList,deptList,treeProps,tools,labelStyle,companyList, isAdmin, companyInfo, caluseList } = toRefs(data);
|
const userInfo = ref()
|
onMounted(async ()=>{
|
await getSysClauseList()
|
if(userStore.roles.includes('admin')){
|
data.isAdmin = true
|
await getCompanyList()
|
}else{
|
data.isAdmin = false
|
data.queryParams.companyId = userStore.companyId
|
}
|
await getList()
|
})
|
|
onUnmounted(()=>{
|
|
})
|
|
const getSysClauseList = async ()=> {
|
const res = await getSysClause()
|
if(res.code == 200){
|
data.caluseList = res.data.map(i=>{
|
return {
|
clauseNum: i.clauseNum,
|
content: i.name
|
}
|
})
|
}else{
|
ElMessage.warning(res.message)
|
}
|
}
|
|
const getList = async () => {
|
loading.value = true
|
const res = await getStandardQuality(data.queryParams)
|
if(res.code == 200){
|
data.dataList = res.data.data || []
|
data.total = res.data.total
|
}else{
|
ElMessage.warning(res.message)
|
}
|
loading.value = false
|
}
|
|
const getDeptData = async (val) => {
|
await getDeptList(val)
|
const res = await getDistribution({companyId: val.companyId})
|
if(res.code == 200){
|
for(let item of data.caluseList){
|
const sameNum = data.companyInfo.duties.filter(i=>i.clauseNum == item.clauseNum).map(j=> {
|
return {
|
deptId: j.deptId,
|
chooseLab: j.chooseLab
|
}
|
}
|
)
|
for(let i of sameNum){
|
item[i.deptId] = i.chooseLab? (i.chooseLab==1?'●':'○'):'○'
|
}
|
}
|
}else{
|
ElMessage.warning(res.message)
|
}
|
}
|
const getDeptList = async (val) => {
|
const res = await getDepart({responsType: 1, companyId: val.companyId})
|
if(res.code == 200){
|
data.originDeptList = JSON.parse(JSON.stringify(res.data))
|
data.deptList.deptName = val.companyName
|
data.deptList.children = proxy.handleTree(res.data, "deptId")
|
}else{
|
ElMessage.warning(res.message)
|
}
|
}
|
const addFile = async ()=>{
|
let params={}
|
if(data.queryParams.companyId){
|
params = {
|
qualityName: '质量手册',
|
companyId: data.queryParams.companyId
|
}
|
}else{
|
ElMessage.warning('请先选择对应的企业')
|
return
|
}
|
const res = await addStandardQuality(params)
|
if(res.code == 200){
|
ElMessage.success(res.message)
|
await getList()
|
}else{
|
ElMessage.warning(res.message)
|
}
|
}
|
|
const getCompanyList = async ()=>{
|
const queryParams = {
|
pageNum: 1,
|
pageSize: 999
|
}
|
const res = await getCompany(queryParams)
|
if (res.code == 200) {
|
data.companyList = res.data.list?res.data.list:[]
|
// data.queryParams.companyId = data.companyList[0].id
|
} else {
|
ElMessage.warning(res.message)
|
}
|
}
|
|
const getInfo = async (val) => {
|
const res = await getStandardDetail({companyId: val.companyId});
|
if(res.code === 200){
|
if(!res.data || (res.data.companyIndustryTemplates.length == 0 && res.data.companyQualityPolicies.length == 0 && res.data.companySummaries.length == 0 && res.data.sysFunctionalDistributions
|
.length == 0 && res.data.treeSelects.length == 0)){
|
loading.value = false
|
return Promise.reject(new Error('该企业暂无质量数据'))
|
}
|
data.companyInfo.summaries = res.data.companySummaries ? res.data.companySummaries[0]?.companySummary : []
|
data.companyInfo.policies = res.data.companyQualityPolicies ? res.data.companyQualityPolicies[0]?.policy : []
|
const duties = transToTableData(res.data.sysFunctionalDistributions,data.originDeptList)
|
data.companyInfo.allDepts = duties.allDepts
|
console.log(data.companyInfo.allDepts,'all')
|
data.companyInfo.clauses = duties.clauses
|
data.companyInfo.temps = res.data.companyIndustryTemplates?.map((item,index)=>{
|
return {
|
index: index + 1,
|
templateName: item.templateName
|
}
|
}) || []
|
}else{
|
ElMessage.warning(res.message)
|
}
|
}
|
|
const transToTableData=(duties,deptList)=>{
|
// 步骤1:获取所有唯一的部门和条款编号
|
// const allDeptNames = [...new Set(duties.map(item => item.deptName))];
|
const allDepts = deptList
|
const allClauseNums = [...new Set([
|
...data.caluseList.map(c => c.clauseNum),
|
...duties.map(d => d.clauseNum)
|
])];
|
|
// 步骤2:为每个条款生成完整的部门数据(缺失数据默认 chooseLab: 0)
|
const mergeValues = ['4','5','6','7','7.1','8','8.2','8.3','8.4','8.5','9','9.1','10']
|
const processedClauses = allClauseNums.map(clauseNum => {
|
const clauseContent = data.caluseList.find(c => c.clauseNum === clauseNum)?.content || "";
|
// 为当前条款生成所有部门的数据(确保每个部门都有值)
|
const deptValues = allDepts.map(dept => {
|
const matchedDept = duties.find(
|
item => item.clauseNum === clauseNum && item.deptId === dept.deptId
|
)
|
if(dept.deptType == '0'){
|
return matchedDept ? (matchedDept.chooseLab==1?'●':'○' ): '○'
|
}else{
|
return matchedDept ? (matchedDept.chooseLab==1?'▲':'○' ): '○'
|
}
|
});
|
return {
|
clauseNum,
|
content: clauseContent,
|
deptValues: !mergeValues.includes(clauseNum)? deptValues : deptValues.map(i=> '')
|
}
|
})
|
// 最终数据结构
|
return {
|
clauses: processedClauses,
|
allDepts // 用于生成表头
|
};
|
}
|
|
const initFile = async (val) => {
|
data.companyInfo = {}
|
loading.value = true
|
if(val.filePath !== ''){
|
window.open(import.meta.env.VITE_APP_BASE_API + '/' + val.filePath)
|
}else{
|
try {
|
await getDeptList(val)
|
await getInfo(val)
|
// 2. 等待DOM更新完成
|
await nextTick();
|
|
// 3. 捕获组织结构图图片
|
const orgChartImage = await captureElementToImage('org-tree-container');
|
data.companyInfo.orgChart = orgChartImage
|
data.companyInfo.companyName = val.companyName
|
const templatePath = '/qualityFile.docx'
|
await generateWordDocument(
|
templatePath,
|
data.companyInfo,
|
val.companyName+'质量手册.docx'
|
);
|
loading.value = false
|
} catch (error) {
|
console.error('操作失败:', error);
|
ElMessage.warning(error.message || '操作失败');
|
}
|
}
|
}
|
|
const captureElementToImage = async (elementId) => {
|
const html2canvas = (await import('html2canvas')).default;
|
const element = document.getElementById(elementId);
|
if (!element) throw new Error('找不到组织结构图容器');
|
|
return await html2canvas(element, {
|
scale: 4,
|
useCORS: true,
|
allowTaint: true,
|
backgroundColor: 'rgba(0,0,0,0)' // 确保背景是白色
|
}).then(canvas => canvas.toDataURL('image/png'));
|
}
|
|
const openDialog = (type, value) => {
|
dialogRef.value.openDialog(type, value);
|
}
|
|
/** 重置新增的表单以及其他数据 */
|
const reset= async()=> {
|
data.queryParams = {
|
pageNum: 1,
|
pageSize: 10,
|
companyId: null
|
}
|
await getCompanyList()
|
await getList()
|
}
|
const handleDelete = (val) => {
|
ElMessageBox.confirm(
|
'确定删除此条数据?',
|
'提示',
|
{
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning',
|
})
|
.then( async() => {
|
const res = await delStandardQuality({standardizedQualityId: val.id})
|
if(res.code == 200){
|
ElMessage.success('数据删除成功')
|
await getList()
|
}else{
|
ElMessage.warning(res.message)
|
}
|
})
|
}
|
|
</script>
|
<style lang="scss">
|
.orgTreeBox{
|
width: 700px;
|
height: 700px;
|
position: absolute; /* 或 fixed */
|
left: -9999px; /* 移出可视区域 */
|
pointer-events: none; /* 禁止交互 */
|
z-index: -1;
|
}
|
</style>
|