Files
pqs-9100_client/frontend/src/views/plan/planList/index.vue
2025-07-24 08:29:03 +08:00

546 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class='table-box' ref='popupBaseView'>
<ProTable
ref='proTable'
:columns='columns'
:data="currentPageData"
:request-api='getTableList'
:pagination="false"
>
<!-- 表格 header 按钮 -->
<template #tableHeader='scope'>
<el-button type='primary' v-auth.plan="'import'" :icon='Download' @click='importClick' v-if="modeStore.currentMode != '比对式'">导入</el-button>
<el-button type='primary' v-auth.plan="'export'" :icon='Upload' @click='exportClick' v-if="modeStore.currentMode != '比对式'">导出</el-button>
<!-- <el-button type='primary' v-auth.plan="'combine'" :icon='ScaleToOriginal' :disabled='!(scope.selectedList.length > 1)' @click='combineClick'>
合并
</el-button> -->
<el-button type='primary' v-auth.plan="'add'" :icon='CirclePlus' @click="openDialog('add')">新增</el-button>
<el-button type='primary' v-auth.plan="'analysis'" :icon='List' :disabled='!scope.isSelected' @click='statisticalAnalysisMore(scope.selectedListIds,scope.selectedList)'>统计分析</el-button>
<el-button type='danger' v-auth.plan="'delete'" :icon='Delete' plain :disabled='!scope.isSelected' @click='batchDelete(scope.selectedListIds)'>
删除
</el-button>
</template>
<!-- 表格操作 -->
<template #operation='scope'>
<el-button type='primary' v-auth.plan="'edit'" link :icon='EditPen' @click="openDialog('edit',scope.row)">编辑</el-button>
<el-button type='primary' v-auth.plan="'delete'" link :icon='Delete' @click='handleDelete(scope.row)'>删除</el-button>
<el-button type='primary' link :icon='List' @click="openChildrenPlan(scope.row)" v-if="modeStore.currentMode == '比对式' && scope.row.fatherPlanId == 0">子计划</el-button>
<!-- <el-button type='primary' link :icon='List' @click='showDeviceOpen(scope.row)'>设备绑定</el-button> -->
<el-button type='primary' v-auth.plan="'analysis'" link :icon='List' v-if="scope.row.testState == '2'" @click='statisticalAnalysis(scope.row)'>统计分析</el-button>
</template>
<!-- 在此处新增 footer 插槽 -->
<template #pagination ='scope'>
<el-pagination
:background="true"
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="[10, 25, 50, 100]"
:total="pageTotal"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
</ProTable>
</div>
<!-- 向计划导入/导出设备对话框 -->
<PlanPopup :refresh-table='refreshTable' ref='planPopup'/>
<!-- 查看误差体系详细信息 -->
<ErrorStandardPopup :refresh-table='proTable?.getTableList' ref="errorStandardPopup"/>
<!-- 查看检测源 -->
<TestSourcePopup :refresh-table='proTable?.getTableList' ref="testSourcePopup"/>
<ImportExcel ref='planImportExcel'/>
<ChildrenPlan :refresh-table='refreshTable' ref='childrenPlanView' :width='viewWidth' :height='viewHeight'></ChildrenPlan>
</template>
<script setup lang='tsx' name='useProTable'>
import ProTable from '@/components/ProTable/index.vue'
import type { ProTableInstance, ColumnProps, RenderScope } from '@/components/ProTable/interface'
import { ScaleToOriginal, CirclePlus, Delete, EditPen, View, Upload, Download, List, Tools } from '@element-plus/icons-vue'
import {getPlanList,deletePlan,exportPlan,downloadTemplate,importPlan } from '@/api/plan/plan.ts'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import type { Plan } from '@/api/plan/interface'
import PlanPopup from '@/views/plan/planList/components/planPopup.vue' // 导入子组件
import DeviceOpen from '@/views/plan/planList/components/devPopup.vue'
import ChildrenPlan from '@/views/plan/planList/components/childrenPlan.vue'
import { useViewSize } from '@/hooks/useViewSize'
import { useRouter } from 'vue-router'
import { useDictStore } from '@/stores/modules/dict'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { Action } from 'element-plus'
import type { ErrorSystem } from '@/api/error/interface'
import ErrorStandardPopup from '@/views/machine/errorSystem/components/errorStandardPopup.vue' // 导入子组件
import TestSourcePopup from '@/views/machine/testSource/components/testSourcePopup.vue' // 导入子组件
import { type TestSource } from '@/api/device/interface/testSource'
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode'; // 引入模式 store
import { useHandleData } from '@/hooks/useHandleData'
import { dictTestState,dictReportState,dictResult } from '@/api/plan/planData.ts'
import {getTestSourceById} from '@/api/device/testSource/index'
import ImportExcel from '@/components/ImportExcel/index.vue'
import {useDownload} from "@/hooks/useDownload";
import {getTestConfig } from '@/api/system/base/index'
import {type Base } from '@/api/system/base/interface'
import { getBoundPqDevList ,staticsAnalyse} from '@/api/plan/plan.ts'
// defineOptions({
// name: 'planList'
// })
const dictStore = useDictStore()
const childrenPlanView = ref()
// ProTable 实例
const proTable = ref<ProTableInstance>()
const errorStandardPopup = ref()
const testSourcePopup = ref()
const planPopup = ref()
const modeStore = useModeStore();
const tableData = ref<any[]>([])
const planImportExcel = ref<InstanceType<typeof ImportExcel> | null>(null)
const { popupBaseView, viewWidth, viewHeight } = useViewSize()
const pageTotal = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const currentPageData = ref<any[]>([])//当前页的数据
const patternId = ref('')
onMounted(async () => {
refreshTable()
})
//假分页后用data刷新
const refreshTable = async () => {
try {
console.log("表格刷新")
patternId.value = dictStore.getDictData('Pattern').find(item => item.name === modeStore.currentMode)?.id;
const result = await getPlanList({'patternId' : patternId.value});
tableData.value = buildTree(result.data as any[]);
pageTotal.value = tableData.value.length;
updateCurrentPageData(tableData.value)
console.log("表格刷新成功")
} catch (error) {
tableData.value = [];
ElMessage.error('获取计划列表失败');
}
}
// 监听 tableData 变化
watch(() => tableData.value, (newVal) => {
if (childrenPlanView.value && newVal) {
console.log('监听 tableData 变化', newVal)
childrenPlanView.value.handleTableDataUpdate?.(newVal)
updateCurrentPageData(newVal)
}
}, { deep: true, immediate: true })
//模糊查询计划列表
const getTableList = async(params: any) => {
let newParams = JSON.parse(JSON.stringify(params))
// 从 newParams 中提取筛选条件
const name = newParams.name
const reportState = newParams.reportState
const result = newParams.result
const testState = newParams.testState
// 对 tableData.value 进行筛选
const filteredData = tableData.value.filter(item => {
let match = true
if (name) {
match = item.name?.includes(name)
}
if (reportState !== undefined && reportState !== null && reportState !== '') {
match = item.reportState == (reportState)
}
if (result !== undefined && result !== null && result !== '') {
match = item.result == (result)
}
if (testState !== undefined && testState !== null && testState !== '') {
match = item.testState == (testState)
}
return match
})
// 更新分页总数
pageTotal.value = filteredData.length
// 更新当前页数据
currentPage.value = 1
updateCurrentPageData(filteredData)
}
function buildTree(flatList: any[]): any[] {
const map = new Map();
const tree: any[] = [];
// First, create a map of all items by id for fast lookup
flatList.forEach(item => {
map.set(item.id, { ...item, children: [] });
});
// Then, assign each item to its parent's children array
flatList.forEach(item => {
if (item.fatherPlanId && map.has(item.fatherPlanId)) {
map.get(item.fatherPlanId).children.push(map.get(item.id));
} else if (item.fatherPlanId === '0') {
// Items with fatherPlanId '0' are root nodes
tree.push(map.get(item.id));
}
});
return tree;
}
const handleSizeChange = (size: number) => {
pageSize.value = size
updateCurrentPageData()
}
const handleCurrentChange = (page: number) => {
currentPage.value = page
updateCurrentPageData()
}
const updateCurrentPageData = (data = tableData.value) => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
currentPageData.value = data.slice(start, end)
console.log('currentPageData', currentPageData.value)
}
const dataSourceType = computed(() => {
// switch (modeStore.currentMode) {
// case '模拟式':
// return 'Datasource_Simulate'
// case '数字式':
// return 'Datasource_Digital'
// default:
// return 'Datasource_Contrast'
// }
return 'Datasource'
})
// 表格配置项
const columns = reactive<ColumnProps<Plan.ReqPlan>[]>([
{ type: 'selection', fixed: 'left', minWidth: 70 },
{ type: 'index', fixed: 'left', minWidth: 70, label: '序号'},
{
prop: 'name',
label: '名称',
minWidth: 200,
search: { el: 'input' },
},
{
prop: 'testState',
label: '检测状态',
minWidth: 100,
enum:dictTestState,
search: { el:'select',props: { filterable: true }},
fieldNames: { label: 'label', value: 'id' },
render: scope => {
return (
scope.row.testState === 0 ? <el-tag type='warning' effect="dark">未检</el-tag> :
scope.row.testState === 1 ? <el-tag type='danger' effect="dark">检测中</el-tag> :
<el-tag type='success' effect="dark">检测完成</el-tag>
)
},
},
{
prop: 'progress',
label: '检测进度',
minWidth: 150,
render: (scope) => {
return (
<el-progress
text-inside={true}
stroke-width={20}
percentage={(scope.row.progress ?? 0) * 100}
status='success'
/>
);
},
},
{
prop: 'reportState',
label: '检测报告状态',
minWidth: 120,
enum:dictReportState,
search: { el: 'select', props: { filterable: true } },
fieldNames: { label: 'label', value: 'id' },
render: scope => {
return (
scope.row.reportState === 0 ? '未生成' : scope.row.reportState === 1 ? '部分生成' : '全部生成'
)
},
},
{
prop: 'result',
label: '检测结果',
minWidth: 100,
enum:dictResult,
search: { el: 'select', props: { filterable: true } },
fieldNames: { label: 'label', value: 'id' },
render: scope => {
return (
scope.row.result === 0 ? '不符合' : scope.row.result === 1 ? '符合' : '/'
)
},
},
{
prop: 'createTime',
label: '创建时间',
minWidth: 180,
render: scope => {
if (scope.row.createTime) {
const date = new Date(scope.row.createTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
return '';
}
},
{
prop: 'sourceName',
label: '检测源',
minWidth: 300,
isShow: modeStore.currentMode != "比对式",
render: scope => {
const sourceNames = Array.isArray(scope.row.sourceName) ? scope.row.sourceName : [];
const sourceIds = Array.isArray(scope.row.sourceIds) ? scope.row.sourceIds : [];
const firstSourceName = sourceNames[0];
const firstSourceId = sourceIds[0];
const remainingSourceNames = sourceNames.slice(1);
const remainingSourceIds = sourceIds.slice(1);
return (
<div class='flx-flex-start'>
{firstSourceName && (
<el-link type='primary' link onClick={() => showTestSource(firstSourceId)}>
{firstSourceName}
</el-link>
)}
<moreButtons isShow={isVisible(scope.row)}
sourceNames={remainingSourceNames}//长度>1传给更多子组件从第二位开始的名字
sourceIds={remainingSourceIds} //长度>1时传给更多子组件从第二位开始的id
onSource-clicked={showTestSource}
/>
</div>
)
},
},
{
prop: 'scriptId',
label: '检测脚本',
minWidth: 300,
isShow: modeStore.currentMode != "比对式",
render: scope => {
return (
<span>{scope.row.scriptName}</span>
)
},
},
{
prop: 'standardDevNameStr',
label: '标准设备',
minWidth: 250,
isShow: modeStore.currentMode == "比对式",
render: scope => {
const standardDevNameStr = scope.row.standardDevNameStr
if (!standardDevNameStr) {
return '/'
}
return standardDevNameStr
}
},
{
prop: 'testItemNameStr',
label: '测试项',
minWidth: 300,
isShow: modeStore.currentMode == "比对式",
},
{
prop: 'errorSysId',
label: '误差体系', minWidth:300,
render: scope => {
return (
<el-link type='primary' link onClick={() => showData(scope.row)}>
{scope.row.errorSysName}
</el-link>
)
},
},
{
prop: 'origin',//null 表示主计划 1表示子计划
label: '来源',
minWidth:200,
isShow: modeStore.currentMode == "比对式",
},
{
prop: 'datasourceIds',
label: '数据源',
enum: dictStore.getDictData(dataSourceType.value),
fieldNames: { label: 'name', value: 'value' },
minWidth: 230,
render: (scope) => {
const codes = scope.row.datasourceIds // 获取当前行的 datasourceIds 字段
if (!codes) {
return '/'
}
// 确保 codes 是一个字符串
const codeString = Array.isArray(codes) ? codes.join(',') : codes
const codeArray = codeString.split(',')
if (codeArray.length > 1) {
// 查找与每个 code 值匹配的 name然后拼接成逗号分割的字符串
const names = codeArray.map(code => {
const dictItem = dictStore.getDictData(dataSourceType.value).find(item => item.code === code)
return dictItem ? dictItem.name : '/' // 如果找到匹配的项,返回对应的 name
})
return names.join(', ') // 用逗号连接所有的 name
}
// 查找单个 code 对应的 name
const dictItem = dictStore.getDictData(dataSourceType.value).find(item => item.code === codeArray[0])
return dictItem ? dictItem.name : '/' // 如果找到匹配的项,返回对应的 name
},
},
{
prop: 'dataRule',
label: '数据处理原则',
enum: dictStore.getDictData('Data_Rule'),
fieldNames: { label: 'name', value: 'id' },
minWidth: 120,
},
{ prop: 'operation', label: '操作', fixed: 'right', minWidth: 250 },
])
function isVisible(row: Plan.ReqPlan) {
if(!row.hasOwnProperty('sourceName') || !Array.isArray(row.sourceName)){
return false
}
else if(row.sourceName.length <= 1){
return false
}
else{
return true
}
}
function showData(row: any) {
let split = row.errorSysName.split('-')
errorStandardPopup.value?.open(row.errorSysName, {id:row.errorSysId,devLevel:split[split.length-1]})
}
async function showTestSource(row:string) {
const patternId = dictStore.getDictData('Pattern').find(item=>item.name=== modeStore.currentMode)?.id ?? ''//获取数据字典中对应的id
const params: TestSource.ResTestSource = {
id: row, // 根据实际情况设
pattern: patternId,
type: '',
devType: '',
state: 0
};
const result = await getTestSourceById(params);
testSourcePopup.value?.open('view', result.data, modeStore.currentMode);
}
// 点击导入按钮
const importClick = () => {
const params = {
title: '检测计划',
showCover: false,
patternId: dictStore.getDictData('Pattern').find(item=>item.name=== modeStore.currentMode)?.id ?? '',
tempApi: downloadTemplate,
importApi: importPlan,
getTableList: refreshTable(),
}
planImportExcel.value?.acceptParams(params)
}
// 点击导出按钮
const exportClick = () => {
ElMessageBox.confirm('确认导出检测计划?', '温馨提示', { type: 'warning' }).then(() =>{
useDownload(exportPlan,'检测计划导出数据', {...proTable.value?.searchParam,patternId:patternId}, false,'.xlsx')
})
}
// 打开 drawer(新增、编辑)
const openDialog = (titleType: string, row: Partial<Plan.ReqPlan> = {}) => {
if(modeStore.currentMode == '比对式'){
if(row.children?.length > 0){
planPopup.value?.open(titleType, row,modeStore.currentMode,0)//0主计划 1子计划 2修改子计划
}else{
planPopup.value?.open(titleType, row,modeStore.currentMode,2)//0主计划 1子计划 2修改子计划
}
}else{
planPopup.value?.open(titleType, row,modeStore.currentMode,0)//0主计划 1子计划 2修改子计划
}
}
// 批量删除设备
const batchDelete = async (id: string[]) => {
await useHandleData(deletePlan, {'id':id, 'pattern':patternId.value},'删除所选检测计划')
proTable.value?.clearSelection()
await refreshTable()
}
// 删除检测计划
const handleDelete = async (params: Plan.ReqPlanParams) => {
await useHandleData(deletePlan, {id: [params.id], 'pattern': patternId.value}, `删除【${params.name}】检测计划`)
proTable.value?.clearSelection()
await refreshTable()
}
const openChildrenPlan = (row: Partial<Plan.ReqPlan> = {}) => {
childrenPlanView.value.open("检测计划详情",row,patternId.value)
}
const statisticalAnalysisMore = async (ids: string[], rows: Plan.ReqPlan[]) => {
const hasInvalidState = rows.some(row => row.testState != 2);
if (hasInvalidState) {
ElMessage({
type: 'error',
message: '存在检测状态不为检测完成的计划,无法进行统计分析。',
});
proTable.value?.clearSelection()
return;
}
useDownload(staticsAnalyse,'分析结果', ids, false,'.xlsx')
}
const statisticalAnalysis = async (row: Partial<Plan.ReqPlan> = {}) => {
useDownload(staticsAnalyse,'分析结果', [row.id], false,'.xlsx')
}
</script>
<style scoped>
</style>