|
|
@@ -9,231 +9,192 @@
|
|
|
append-to-body
|
|
|
width="80%"
|
|
|
:maxable="true"
|
|
|
+ @update:fullscreen="handleFullscreenChange"
|
|
|
>
|
|
|
- <el-table :data="tableData" :default-expand-all="true">
|
|
|
+ <el-table
|
|
|
+ ref="mainTable"
|
|
|
+ :data="tableData"
|
|
|
+ :expand-row-keys="expandedKeys"
|
|
|
+ :row-class-name="rowClassName"
|
|
|
+ :row-key="row => row.__rowKey"
|
|
|
+ :height="tableHeight"
|
|
|
+ @scroll="handleScroll"
|
|
|
+ @expand-change="handleExpandChange"
|
|
|
+ >
|
|
|
<el-table-column type="expand">
|
|
|
<template slot-scope="props">
|
|
|
- <div style="padding: 10px">
|
|
|
- <el-table :data="props.row.qualitySampleTemplateList" border>
|
|
|
- <el-table-column
|
|
|
- label="检测方案编码"
|
|
|
- prop="qualitySchemeTemplateCode"
|
|
|
- align="center"
|
|
|
- show-overflow-tooltip
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <span :class="scope.row.qualityResults === 2 ? 'warn' : ''">
|
|
|
- {{ scope.row.qualitySchemeTemplateCode }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- label="检测方案名称"
|
|
|
- prop="qualitySchemeTemplateName"
|
|
|
- align="center"
|
|
|
- show-overflow-tooltip
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <span :class="scope.row.qualityResults === 2 ? 'warn' : ''">
|
|
|
- {{ scope.row.qualitySchemeTemplateName }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- label="检测类型"
|
|
|
- prop="categoryLevelClassName"
|
|
|
- align="center"
|
|
|
- show-overflow-tooltip
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <span :class="scope.row.qualityResults === 2 ? 'warn' : ''">
|
|
|
- {{ scope.row.categoryLevelClassName }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- label="检测项编码"
|
|
|
- prop="inspectionCode"
|
|
|
- align="center"
|
|
|
- show-overflow-tooltip
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <span :class="scope.row.qualityResults === 2 ? 'warn' : ''">
|
|
|
- {{ scope.row.inspectionCode }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- label="检测项名称"
|
|
|
- prop="inspectionName"
|
|
|
- align="center"
|
|
|
- show-overflow-tooltip
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <span :class="scope.row.qualityResults === 2 ? 'warn' : ''">
|
|
|
- {{ scope.row.inspectionName }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- label="工艺参数"
|
|
|
- prop="defaultValue"
|
|
|
- align="center"
|
|
|
- show-overflow-tooltip
|
|
|
- min-width="220px"
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <!-- <span :class="scope.row.qualityResults === 2 ? 'warn' : ''">
|
|
|
- {{ scope.row.defaultValue }}
|
|
|
- </span> -->
|
|
|
+ <div class="virtual-expand-wrapper">
|
|
|
+ <!-- 表头 -->
|
|
|
+ <div class="virtual-table-header">
|
|
|
+ <div class="cell">检测方案编码</div>
|
|
|
+ <div class="cell">检测方案名称</div>
|
|
|
+ <div class="cell">检测类型</div>
|
|
|
+ <div class="cell">检测项编码</div>
|
|
|
+ <div class="cell">检测项名称</div>
|
|
|
+ <div class="cell wide">工艺参数</div>
|
|
|
+ <div class="cell">检测工具</div>
|
|
|
+ <div class="cell input-col">检测内容</div>
|
|
|
+ <div class="cell">检测结果</div>
|
|
|
+ </div>
|
|
|
+ <!-- 虚拟滚动表体 -->
|
|
|
+ <RecycleScroller
|
|
|
+ class="virtual-scroller"
|
|
|
+ :items="props.row.qualitySampleTemplateList || []"
|
|
|
+ :item-size="64"
|
|
|
+ key-field="__vsKey"
|
|
|
+ :buffer="200"
|
|
|
+ v-if="(props.row.qualitySampleTemplateList || []).length"
|
|
|
+ >
|
|
|
+ <template #default="{ item }">
|
|
|
+ <div
|
|
|
+ class="virtual-row"
|
|
|
+ :class="{ warn: item.qualityResults === 2 }"
|
|
|
+ >
|
|
|
<div
|
|
|
- style="display: flex; justify-content: center"
|
|
|
- :class="scope.row.qualityResults === 2 ? 'warn' : ''"
|
|
|
+ class="cell"
|
|
|
+ :title="item.qualitySchemeTemplateCode"
|
|
|
>
|
|
|
- <span v-if="scope.row.textType != 8">{{
|
|
|
- scope.row.symbol
|
|
|
- }}</span>
|
|
|
- <span v-if="scope.row.textType == 3">
|
|
|
- {{ scope.row.minValue }}-{{ scope.row.maxValue }}
|
|
|
- </span>
|
|
|
- <span v-else-if="scope.row.textType == 8">
|
|
|
- 标准值:{{ scope.row.defaultValue
|
|
|
- }}{{ scope.row.unitName || '' }} 下限:{{
|
|
|
- scope.row.minValue || 0
|
|
|
- }}{{ scope.row.unitName || '' }} 上限:{{
|
|
|
- scope.row.maxValue || 0
|
|
|
- }}{{ scope.row.unitName || '' }}
|
|
|
- </span>
|
|
|
- <span v-else>
|
|
|
- <span>{{ scope.row.defaultValue }}</span>
|
|
|
- </span>
|
|
|
- <span v-if="scope.row.textType != 8">
|
|
|
- {{ scope.row.unitName }}
|
|
|
- </span>
|
|
|
+ {{ item.qualitySchemeTemplateCode }}
|
|
|
</div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="检测工具" prop="" align="center">
|
|
|
- <template slot-scope="scope">
|
|
|
- <toolButtom
|
|
|
- v-model="scope.row.useTools"
|
|
|
- :type="
|
|
|
- !pageName && props.row.correlationId ? 'detail' : type
|
|
|
- "
|
|
|
- :sList="scope.row.toolList"
|
|
|
- />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <!-- @blur="(e) => inputResultContent(e, scope.row)" -->
|
|
|
- <el-table-column
|
|
|
- label="检测内容"
|
|
|
- prop="qualityResultContent"
|
|
|
- align="center"
|
|
|
- fixed="right"
|
|
|
- width="360"
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <el-input
|
|
|
- :class="scope.row.qualityResults === 2 ? 'warn' : ''"
|
|
|
- v-model="scope.row.qualityResultContent"
|
|
|
- placeholder="请输入内容"
|
|
|
- :disabled="
|
|
|
- type == 'detail' || (!pageName && props.row.correlationId)
|
|
|
- "
|
|
|
- @input="
|
|
|
- clientEnvironmentId == 10
|
|
|
- ? newHandleInput(
|
|
|
- props.row.qualitySampleTemplateList,
|
|
|
- props.$index
|
|
|
- )
|
|
|
- : handleInput(
|
|
|
- scope.row,
|
|
|
- scope.row.qualityResultContent,
|
|
|
- tableData
|
|
|
- )
|
|
|
- "
|
|
|
+ <div
|
|
|
+ class="cell"
|
|
|
+ :title="item.qualitySchemeTemplateName"
|
|
|
>
|
|
|
- <template slot="append" v-if="scope.row.unitName">
|
|
|
- {{ scope.row.unitName }}
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- label="检测结果"
|
|
|
- prop="qualityResults"
|
|
|
- align="center"
|
|
|
- fixed="right"
|
|
|
- >
|
|
|
- <template slot-scope="scope">
|
|
|
- <el-select
|
|
|
- v-if="
|
|
|
- !['炉水电导率', '给水电导率'].includes(
|
|
|
- scope.row.inspectionName
|
|
|
- )
|
|
|
- "
|
|
|
- v-model="scope.row.qualityResults"
|
|
|
- placeholder="请选择"
|
|
|
- style="width: 100%"
|
|
|
- @change="qualityResultsListChange(tableData)"
|
|
|
- :disabled="
|
|
|
- type == 'detail' || (!pageName && props.row.correlationId)
|
|
|
- "
|
|
|
+ {{ item.qualitySchemeTemplateName }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="cell"
|
|
|
+ :title="item.categoryLevelClassName"
|
|
|
>
|
|
|
- <el-option
|
|
|
- v-for="item in qualityResultsList"
|
|
|
- :key="item.value"
|
|
|
- :label="item.label"
|
|
|
- :value="item.value"
|
|
|
+ {{ item.categoryLevelClassName }}
|
|
|
+ </div>
|
|
|
+ <div class="cell" :title="item.inspectionCode">
|
|
|
+ {{ item.inspectionCode }}
|
|
|
+ </div>
|
|
|
+ <div class="cell" :title="item.inspectionName">
|
|
|
+ {{ item.inspectionName }}
|
|
|
+ </div>
|
|
|
+ <div class="cell wide">
|
|
|
+ <div
|
|
|
+ style="
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ "
|
|
|
+ :class="item.qualityResults === 2 ? 'warn' : ''"
|
|
|
+ >
|
|
|
+ <span v-if="item.textType != 8">{{ item.symbol }}</span>
|
|
|
+ <span v-if="item.textType == 3">
|
|
|
+ {{ item.minValue }}-{{ item.maxValue }}
|
|
|
+ </span>
|
|
|
+ <span v-else-if="item.textType == 8">
|
|
|
+ 标准值:{{ item.defaultValue
|
|
|
+ }}{{ item.unitName || '' }} 下限:{{
|
|
|
+ item.minValue || 0
|
|
|
+ }}{{ item.unitName || '' }} 上限:{{
|
|
|
+ item.maxValue || 0
|
|
|
+ }}{{ item.unitName || '' }}
|
|
|
+ </span>
|
|
|
+ <span v-else>
|
|
|
+ <span>{{ item.defaultValue }}</span>
|
|
|
+ </span>
|
|
|
+ <span v-if="item.textType != 8">
|
|
|
+ {{ item.unitName }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="cell">
|
|
|
+ <toolButtom
|
|
|
+ v-model="item.useTools"
|
|
|
+ :type="
|
|
|
+ !pageName && props.row.correlationId ? 'detail' : type
|
|
|
+ "
|
|
|
+ :sList="item.toolList"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="cell input-col">
|
|
|
+ <el-input
|
|
|
+ :class="item.qualityResults === 2 ? 'warn' : ''"
|
|
|
+ v-model="item.qualityResultContent"
|
|
|
+ placeholder="请输入内容"
|
|
|
+ :disabled="
|
|
|
+ type == 'detail' ||
|
|
|
+ (!pageName && props.row.correlationId)
|
|
|
+ "
|
|
|
+ @input="
|
|
|
+ clientEnvironmentId == 10
|
|
|
+ ? newHandleInput(
|
|
|
+ props.row.qualitySampleTemplateList,
|
|
|
+ props.$index
|
|
|
+ )
|
|
|
+ : handleInput(
|
|
|
+ item,
|
|
|
+ item.qualityResultContent,
|
|
|
+ tableData
|
|
|
+ )
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <template slot="append" v-if="item.unitName">
|
|
|
+ {{ item.unitName }}
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+ <div class="cell">
|
|
|
+ <el-select
|
|
|
+ v-if="
|
|
|
+ !['炉水电导率', '给水电导率'].includes(
|
|
|
+ item.inspectionName
|
|
|
+ )
|
|
|
+ "
|
|
|
+ v-model="item.qualityResults"
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="qualityResultsListChange(tableData)"
|
|
|
+ :disabled="
|
|
|
+ type == 'detail' ||
|
|
|
+ (!pageName && props.row.correlationId)
|
|
|
+ "
|
|
|
>
|
|
|
- </el-option>
|
|
|
- </el-select>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
+ <el-option
|
|
|
+ v-for="opt in qualityResultsList"
|
|
|
+ :key="opt.value"
|
|
|
+ :label="opt.label"
|
|
|
+ :value="opt.value"
|
|
|
+ >
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </RecycleScroller>
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="empty-tpl"
|
|
|
+ style="
|
|
|
+ padding: 20px;
|
|
|
+ text-align: center;
|
|
|
+ color: #909399;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-top: none;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 暂无检测项
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <template v-for="column in tableColumns">
|
|
|
- <el-table-column
|
|
|
- :label="column.label"
|
|
|
- :prop="column.prop"
|
|
|
- :fixed="column.fixed"
|
|
|
- :show-overflow-tooltip="true"
|
|
|
- :width="column.width"
|
|
|
- :align="column.align"
|
|
|
- :formatter="column.formatter"
|
|
|
- >
|
|
|
- <!-- <template slot-scope="scope" v-if="column.slot=='qualityResults'">
|
|
|
- <el-select
|
|
|
- v-model="scope.row.qualityResults"
|
|
|
- placeholder="请选择"
|
|
|
- style="width: 100%"
|
|
|
- :disabled="type == 'detail'"
|
|
|
- >
|
|
|
- <el-option
|
|
|
- v-for="item in qualityResultsList"
|
|
|
- :key="item.value"
|
|
|
- :label="item.label"
|
|
|
- :value="item.value"
|
|
|
- >
|
|
|
- </el-option>
|
|
|
- </el-select>
|
|
|
- </template> -->
|
|
|
- </el-table-column>
|
|
|
- </template>
|
|
|
- <!-- <el-table-column label="操作" align="center" width="80">
|
|
|
- <template slot-scope="scope">
|
|
|
- <el-link
|
|
|
- :underline="false"
|
|
|
- type="primary"
|
|
|
- @click="handleDispose(scope.$index, scope.row, 'dispose')"
|
|
|
- >
|
|
|
- 处置
|
|
|
- </el-link>
|
|
|
- </template>
|
|
|
- </el-table-column> -->
|
|
|
+ <el-table-column
|
|
|
+ v-for="column in tableColumns"
|
|
|
+ :key="column.prop"
|
|
|
+ :label="column.label"
|
|
|
+ :prop="column.prop"
|
|
|
+ :fixed="column.fixed"
|
|
|
+ :show-overflow-tooltip="true"
|
|
|
+ :width="column.width"
|
|
|
+ :align="column.align"
|
|
|
+ :formatter="column.formatter"
|
|
|
+ >
|
|
|
+ </el-table-column>
|
|
|
</el-table>
|
|
|
<template v-slot:footer v-if="type == 'report'">
|
|
|
<el-button @click="handleClose">取消</el-button>
|
|
|
@@ -244,10 +205,13 @@
|
|
|
|
|
|
<script>
|
|
|
import toolButtom from './toolButtom.vue';
|
|
|
+ import { RecycleScroller } from 'vue-virtual-scroller';
|
|
|
+ import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
|
|
import { handleInput, qualityResultsListChange } from './unit.js';
|
|
|
export default {
|
|
|
components: {
|
|
|
- toolButtom
|
|
|
+ toolButtom,
|
|
|
+ RecycleScroller
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
@@ -322,11 +286,6 @@
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- // { label: '货区', prop: 'areaName', align: 'center', },
|
|
|
- // { label: '货架', prop: 'goodsShelfName', align: 'center', },
|
|
|
- // { label: '货位', prop: 'goodsAllocationName', align: 'center', },
|
|
|
- // { label: '生产日期', prop: 'productionDate', align: 'center', },
|
|
|
- // { label: '采购日期', prop: 'purchaseDate', align: 'center', }
|
|
|
],
|
|
|
qualityResultsList: [
|
|
|
{
|
|
|
@@ -342,56 +301,346 @@
|
|
|
label: '让步接收'
|
|
|
}
|
|
|
],
|
|
|
- clientEnvironmentId:'',
|
|
|
- sList: [] //检测工具筛选
|
|
|
+ clientEnvironmentId: '',
|
|
|
+ sList: [], //检测工具筛选
|
|
|
+ // ==== 滚动懒加载相关 ====
|
|
|
+ sourceMap: new Map(), // sampleCode -> 原始样品(包含完整 qualitySampleTemplateList)
|
|
|
+ expandedKeys: [], // 当前展开的样品 sampleCode 列表
|
|
|
+ loadedKeys: [], // 持久预加载的样品(卸载时跳过,永驻内存)
|
|
|
+ scrollRAF: null, // 滚动节流
|
|
|
+ scrollBound: false, // 滚动监听是否已绑定
|
|
|
+ resizeObserver: null, // 监听 dialog body 大小变化
|
|
|
+ isFullscreen: false, // 当前是否处于全屏
|
|
|
+ DEFAULT_TABLE_HEIGHT: 560, // 默认(非全屏)表格高度
|
|
|
+ tableHeight: 560, // 当前表格高度(全屏时由 recomputeTableHeight 重算)
|
|
|
+ PRELOAD_COUNT: 3 // 预加载样品数(打开弹框时立即加载,滚动时永驻内存不卸载)
|
|
|
};
|
|
|
},
|
|
|
computed: {},
|
|
|
|
|
|
created() {},
|
|
|
+ beforeDestroy() {
|
|
|
+ // 解绑滚动监听
|
|
|
+ this.unbindScrollListener();
|
|
|
+ },
|
|
|
methods: {
|
|
|
async openDia(index, row, type, list, isStatus, sList, pageName) {
|
|
|
this.clientEnvironmentId =
|
|
|
this.$store.state.user.info.clientEnvironmentId;
|
|
|
- this.tableData = [];
|
|
|
this.rowIndex = index;
|
|
|
this.type = type;
|
|
|
this.pageName = pageName;
|
|
|
- // console.log(list, 'lklllllllllllllllllkkkkkkkkkkkkkkkkkkkkk', list[0].qualityResults);
|
|
|
- //如果检测项qualitySampleTemplateList只有一条数据且检测结果是让步接收
|
|
|
- if (list[0].qualitySampleTemplateList.length == 1) {
|
|
|
- for (let i = 0; i < list[0].qualitySampleTemplateList.length; i++) {
|
|
|
- list[0].qualitySampleTemplateList[i].qualityResults = list[0]
|
|
|
- .qualityResults
|
|
|
- ? list[0].qualityResults
|
|
|
- : 1;
|
|
|
+
|
|
|
+ // 1. 保存原始数据到 sourceMap(用于滚动懒加载 templateList)
|
|
|
+ this.sourceMap = new Map();
|
|
|
+ list.forEach((item, idx) => {
|
|
|
+ const key = item.sampleCode || `__idx_${idx}`;
|
|
|
+ this.sourceMap.set(key, item);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 2. 弹框立刻显示,tableData 立即填满所有样品(templateList 懒加载为空)
|
|
|
+ // 所有样品默认展开,expand 区域始终可见;只对可见样品加载 templateList
|
|
|
+ this.tableData = list.map((sample, idx) => {
|
|
|
+ const key = sample.sampleCode || `__idx_${idx}`;
|
|
|
+ return {
|
|
|
+ ...sample,
|
|
|
+ __rowKey: key,
|
|
|
+ qualitySampleTemplateList: []
|
|
|
+ };
|
|
|
+ });
|
|
|
+ this.expandedKeys = list.map(
|
|
|
+ (sample, idx) => sample.sampleCode || `__idx_${idx}`
|
|
|
+ );
|
|
|
+ this.visible = true;
|
|
|
+
|
|
|
+ // 3. 预加载前 N 个样品的检测项(持久 buffer,滚动时不会被卸载)
|
|
|
+ this.loadedKeys = [];
|
|
|
+ const preloadCount = Math.min(this.PRELOAD_COUNT, list.length);
|
|
|
+ for (let i = 0; i < preloadCount; i++) {
|
|
|
+ const key = list[i].sampleCode || `__idx_${i}`;
|
|
|
+ this.loadedKeys.push(key);
|
|
|
+ }
|
|
|
+ // 批量加载模板(loadSampleTemplates 内部有幂等检查,但这里首次加载必然为空数组所以会执行)
|
|
|
+ for (let i = 0; i < preloadCount; i++) {
|
|
|
+ this.loadSampleTemplates(this.loadedKeys[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 下一帧:绑定滚动事件(不再调用 autoExpandVisibleSamples,避免初始就把预加载的卸载掉)
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.bindScrollListener();
|
|
|
+ // 重置全屏状态并重新计算表格高度(防止上次打开残留全屏)
|
|
|
+ this.isFullscreen = false;
|
|
|
+ this.tableHeight = this.DEFAULT_TABLE_HEIGHT;
|
|
|
+ this.observeBodyResize();
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 绑定 el-table body 的滚动事件
|
|
|
+ bindScrollListener() {
|
|
|
+ if (this.scrollBound) return;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const el = this.$refs.mainTable && this.$refs.mainTable.$el;
|
|
|
+ if (!el) return;
|
|
|
+ const body = el.querySelector('.el-table__body-wrapper');
|
|
|
+ if (body) {
|
|
|
+ body.addEventListener('scroll', this.handleScroll);
|
|
|
+ this.scrollBound = true;
|
|
|
}
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 解绑滚动事件 + ResizeObserver
|
|
|
+ unbindScrollListener() {
|
|
|
+ if (this.scrollBound) {
|
|
|
+ const el = this.$refs.mainTable && this.$refs.mainTable.$el;
|
|
|
+ const body = el && el.querySelector('.el-table__body-wrapper');
|
|
|
+ if (body) {
|
|
|
+ body.removeEventListener('scroll', this.handleScroll);
|
|
|
+ }
|
|
|
+ this.scrollBound = false;
|
|
|
}
|
|
|
- // 默认合格
|
|
|
- list.map((item) => {
|
|
|
- item.qualitySampleTemplateList &&
|
|
|
- item.qualitySampleTemplateList.map((el) => {
|
|
|
- el.qualityResults = el.qualityResults ? el.qualityResults : 1;
|
|
|
- });
|
|
|
+ if (this.resizeObserver) {
|
|
|
+ this.resizeObserver.disconnect();
|
|
|
+ this.resizeObserver = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 全屏切换事件(@update:fullscreen)
|
|
|
+ handleFullscreenChange(fullscreen) {
|
|
|
+ this.isFullscreen = !!fullscreen;
|
|
|
+ // 等待 dialog wrap 的 class 切换 + 浏览器布局完成
|
|
|
+ this.$nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ this.recomputeTableHeight();
|
|
|
+ this.observeBodyResize();
|
|
|
+ });
|
|
|
});
|
|
|
- this.tableData = JSON.parse(JSON.stringify(list));
|
|
|
- console.log(this.tableData, '33333333333333333');
|
|
|
+ },
|
|
|
|
|
|
- this.visible = true;
|
|
|
+ // 重新计算表格高度:全屏时填满 dialog body,非全屏回到默认值
|
|
|
+ recomputeTableHeight() {
|
|
|
+ const tableEl = this.$refs.mainTable && this.$refs.mainTable.$el;
|
|
|
+ if (!tableEl) return;
|
|
|
+ if (!this.isFullscreen) {
|
|
|
+ this.tableHeight = this.DEFAULT_TABLE_HEIGHT;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 全屏:沿 DOM 找到 .el-dialog,再取 .el-dialog__body 的内容区高度
|
|
|
+ // (不用 .ele-modal-wrap 这个 class——实际类名是 ele-modal-wrap-fullscreen)
|
|
|
+ const dialog = tableEl.closest('.el-dialog');
|
|
|
+ if (!dialog) return;
|
|
|
+ const body = dialog.querySelector('.el-dialog__body');
|
|
|
+ if (!body) return;
|
|
|
+ const style = getComputedStyle(body);
|
|
|
+ const padTop = parseFloat(style.paddingTop) || 0;
|
|
|
+ const padBottom = parseFloat(style.paddingBottom) || 0;
|
|
|
+ this.tableHeight = body.clientHeight - padTop - padBottom;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 监听 dialog body 尺寸变化(拖拽 / resize / 全屏切换都会触发)
|
|
|
+ observeBodyResize() {
|
|
|
+ if (this.resizeObserver || typeof ResizeObserver === 'undefined') return;
|
|
|
+ const tableEl = this.$refs.mainTable && this.$refs.mainTable.$el;
|
|
|
+ if (!tableEl) return;
|
|
|
+ const body = tableEl.closest('.el-dialog__body');
|
|
|
+ if (!body) return;
|
|
|
+ this.resizeObserver = new ResizeObserver(() => {
|
|
|
+ // 仅当全屏时跟着 resize;非全屏保持固定高度,避免不必要重排
|
|
|
+ if (this.isFullscreen) {
|
|
|
+ this.recomputeTableHeight();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.resizeObserver.observe(body);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 滚动节流
|
|
|
+ handleScroll() {
|
|
|
+ if (this.scrollRAF) return;
|
|
|
+ this.scrollRAF = requestAnimationFrame(() => {
|
|
|
+ this.scrollRAF = null;
|
|
|
+ this.autoExpandVisibleSamples();
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 自动展开可视区的样品行(懒加载其 templateList)
|
|
|
+ autoExpandVisibleSamples() {
|
|
|
+ const table = this.$refs.mainTable;
|
|
|
+ if (!table || !table.store || !table.store.states) return;
|
|
|
+
|
|
|
+ const data = this.tableData;
|
|
|
+ if (!data.length) return;
|
|
|
+
|
|
|
+ // 直接从 DOM 找可视区顶部的行(避免累加高度估算误差)
|
|
|
+ const targetIdx = this.findFirstVisibleRowIdx();
|
|
|
+ if (targetIdx < 0) return;
|
|
|
+
|
|
|
+ // 展开 targetIdx 上下各 1(buffer)
|
|
|
+ const minIdx = Math.max(0, targetIdx - 1);
|
|
|
+ const maxIdx = Math.min(data.length - 1, targetIdx + 1);
|
|
|
+
|
|
|
+ for (let idx = minIdx; idx <= maxIdx; idx++) {
|
|
|
+ const sample = data[idx];
|
|
|
+ if (!sample) continue;
|
|
|
+ const sampleCode = sample.sampleCode || sample.__rowKey;
|
|
|
+ if (!sampleCode) continue;
|
|
|
+ // 所有样品默认都在 expandedKeys 中,expandedKeys.includes 永远为 true,
|
|
|
+ // 直接调用 loadSampleTemplates 即可(内部有 length 幂等检查)
|
|
|
+ this.loadSampleTemplates(sampleCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 卸载已滚出可视区的样品的 templateList(保留展开状态)
|
|
|
+ // loadedKeys 中的样品是预加载 buffer,跳过卸载(永驻内存)
|
|
|
+ const unloadIdx = [];
|
|
|
+ for (let idx = 0; idx < data.length; idx++) {
|
|
|
+ if (idx >= minIdx && idx <= maxIdx) continue;
|
|
|
+ const sample = data[idx];
|
|
|
+ if (!sample) continue;
|
|
|
+ const sampleCode = sample.sampleCode || sample.__rowKey;
|
|
|
+ if (this.loadedKeys.includes(sampleCode)) continue;
|
|
|
+ if (sample.qualitySampleTemplateList &&
|
|
|
+ sample.qualitySampleTemplateList.length) {
|
|
|
+ unloadIdx.push(idx);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unloadIdx.forEach((idx) => {
|
|
|
+ this.$set(
|
|
|
+ this.tableData[idx],
|
|
|
+ 'qualitySampleTemplateList',
|
|
|
+ []
|
|
|
+ );
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 直接从 DOM 找第一个可视的主行(避开累加估算误差和 CSS transition 干扰)
|
|
|
+ // Element UI 的 expand 行有 transition 动画,getBoundingClientRect 测不准,
|
|
|
+ // 所以改用"行底边是否在 body 顶部之下"判断可见性
|
|
|
+ findFirstVisibleRowIdx() {
|
|
|
+ const table = this.$refs.mainTable;
|
|
|
+ if (!table || !table.$el) return -1;
|
|
|
+ const body = table.$el.querySelector('.el-table__body-wrapper');
|
|
|
+ if (!body) return -1;
|
|
|
+
|
|
|
+ const bodyRect = body.getBoundingClientRect();
|
|
|
+ const mainRows = body.querySelectorAll(
|
|
|
+ 'tr.el-table__row:not(.el-table__row--expand)'
|
|
|
+ );
|
|
|
+
|
|
|
+ // 找到第一个底边在 body 顶部之下的行(即可见或部分可见)
|
|
|
+ for (let i = 0; i < mainRows.length; i++) {
|
|
|
+ const rect = mainRows[i].getBoundingClientRect();
|
|
|
+ if (rect.bottom > bodyRect.top) {
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return mainRows.length > 0 ? mainRows.length - 1 : -1;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 懒加载某个样品的 qualitySampleTemplateList
|
|
|
+ loadSampleTemplates(sampleCode) {
|
|
|
+ const idx = this.tableData.findIndex(
|
|
|
+ (s) => (s.sampleCode || s.__rowKey) === sampleCode
|
|
|
+ );
|
|
|
+ if (idx < 0) return;
|
|
|
+ // 已加载过直接返回
|
|
|
+ if (this.tableData[idx].qualitySampleTemplateList.length) return;
|
|
|
+
|
|
|
+ const source = this.sourceMap.get(sampleCode);
|
|
|
+ if (!source || !source.qualitySampleTemplateList) return;
|
|
|
+
|
|
|
+ const tpl = source.qualitySampleTemplateList;
|
|
|
+ const newTpl = new Array(tpl.length);
|
|
|
+ for (let j = 0; j < tpl.length; j++) {
|
|
|
+ newTpl[j] = {
|
|
|
+ __vsKey: `${idx}-${j}-${tpl[j].id || ''}`,
|
|
|
+ ...tpl[j]
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 默认值处理:让步接收 / 默认合格
|
|
|
+ if (tpl.length === 1 && source.qualityResults) {
|
|
|
+ newTpl[0].qualityResults = source.qualityResults || 1;
|
|
|
+ }
|
|
|
+ newTpl.forEach((el) => {
|
|
|
+ if (!el.qualityResults) el.qualityResults = 1;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$set(
|
|
|
+ this.tableData[idx],
|
|
|
+ 'qualitySampleTemplateList',
|
|
|
+ newTpl
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ // 用户点击展开/折叠时(手动触发懒加载)
|
|
|
+ handleExpandChange(row, expandedRows) {
|
|
|
+ const sampleCode = row.sampleCode || row.__rowKey;
|
|
|
+ const isExpanded = expandedRows.some(
|
|
|
+ (r) => (r.sampleCode || r.__rowKey) === sampleCode
|
|
|
+ );
|
|
|
+ if (!isExpanded) {
|
|
|
+ // 折叠:同步从 expandedKeys 中移除,避免高度估算错位
|
|
|
+ this.expandedKeys = this.expandedKeys.filter((k) => k !== sampleCode);
|
|
|
+ const idx = this.tableData.findIndex(
|
|
|
+ (s) => (s.sampleCode || s.__rowKey) === sampleCode
|
|
|
+ );
|
|
|
+ if (idx >= 0) this.$set(this.tableData[idx], 'qualitySampleTemplateList', []);
|
|
|
+ } else {
|
|
|
+ // 展开:加回 expandedKeys(幂等),加载模板
|
|
|
+ if (!this.expandedKeys.includes(sampleCode)) {
|
|
|
+ this.expandedKeys.push(sampleCode);
|
|
|
+ }
|
|
|
+ this.loadSampleTemplates(sampleCode);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 给样品行注入索引 class,用于滚动时定位
|
|
|
+ rowClassName({ row, rowIndex }) {
|
|
|
+ const code = (row && (row.sampleCode || row.__rowKey)) || '';
|
|
|
+ return `sample-row sample-row-${rowIndex} sample-code-${code}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 给样品单元格注入 sampleCode data 属性(备用方案)
|
|
|
+ cellClassName({ row, column, rowIndex, columnIndex }) {
|
|
|
+ if (columnIndex === 0) {
|
|
|
+ return `cell-code-${row.sampleCode || row.__rowKey || ''}`;
|
|
|
+ }
|
|
|
+ return '';
|
|
|
},
|
|
|
|
|
|
handleClose() {
|
|
|
this.visible = false;
|
|
|
+ // 重置懒加载状态
|
|
|
+ this.expandedKeys = [];
|
|
|
+ this.loadedKeys = [];
|
|
|
+ this.sourceMap = new Map();
|
|
|
+ this.tableData = [];
|
|
|
},
|
|
|
handleConfirm() {
|
|
|
+ // 立刻关弹框(用户体验优先),耗时的清理 + emit 推迟到 raf
|
|
|
this.visible = false;
|
|
|
- this.tableData.forEach((item) => {
|
|
|
- if (!this.pageName && item.correlationId) {
|
|
|
- return;
|
|
|
- }
|
|
|
- item.status = 1;
|
|
|
+ this.$nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ this.tableData.forEach((item) => {
|
|
|
+ if (!this.pageName && item.correlationId) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ item.status = 1;
|
|
|
+ });
|
|
|
+ // 清理虚拟列表的辅助字段,避免污染返回数据
|
|
|
+ const cleaned = this.tableData.map((s) => {
|
|
|
+ const tpl = (s.qualitySampleTemplateList || []).map((t) => {
|
|
|
+ const { __vsKey, ...rest } = t;
|
|
|
+ return rest;
|
|
|
+ });
|
|
|
+ return { ...s, qualitySampleTemplateList: tpl };
|
|
|
+ });
|
|
|
+ this.$emit('handleConfirm', cleaned, this.rowIndex);
|
|
|
+ // 重置懒加载状态,与 handleClose 保持一致
|
|
|
+ this.expandedKeys = [];
|
|
|
+ this.loadedKeys = [];
|
|
|
+ this.sourceMap = new Map();
|
|
|
+ });
|
|
|
});
|
|
|
- this.$emit('handleConfirm', this.tableData, this.rowIndex);
|
|
|
},
|
|
|
handleDispose(index, row, type) {
|
|
|
this.$emit('handleDispose', index, row, type);
|
|
|
@@ -402,9 +651,8 @@
|
|
|
(item) => item.inspectionName == '炉水电导率'
|
|
|
)?.qualityResultContent;
|
|
|
let num1 = arr.find(
|
|
|
- (item) => item.inspectionName =='给水电导率'
|
|
|
+ (item) => item.inspectionName == '给水电导率'
|
|
|
)?.qualityResultContent;
|
|
|
- console.log(num, num1);
|
|
|
|
|
|
arr.forEach((item, i) => {
|
|
|
if (num && num1 && item.inspectionName == '干度值') {
|
|
|
@@ -437,4 +685,102 @@
|
|
|
color: red;
|
|
|
}
|
|
|
}
|
|
|
-</style>
|
|
|
+
|
|
|
+ .virtual-expand-wrapper {
|
|
|
+ padding: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .virtual-table-header,
|
|
|
+ .virtual-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 120px 120px 100px 100px 120px 220px 120px 360px 120px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .virtual-table-header {
|
|
|
+ background: #f5f7fa;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-bottom: none;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #909399;
|
|
|
+
|
|
|
+ .cell {
|
|
|
+ padding: 8px 4px;
|
|
|
+ text-align: center;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .virtual-scroller {
|
|
|
+ height: 400px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-top: none;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .virtual-row {
|
|
|
+ height: 64px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ border-left: 1px solid #ebeef5;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ background: #fff;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ &.warn {
|
|
|
+ color: red;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cell {
|
|
|
+ padding: 4px 8px;
|
|
|
+ text-align: center;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 13px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-input {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-input__inner {
|
|
|
+ padding: 0 4px;
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-select {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-input-group__append {
|
|
|
+ padding: 0 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .cell.wide {
|
|
|
+ padding: 4px 8px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .cell.input-col {
|
|
|
+ padding: 4px 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|