419 lines
11 KiB
Vue
Raw Normal View History

2025-05-20 16:34:38 +08:00
<template>
<div class="compact-form">
<el-table
:data="tableData"
border
stripe
highlight-current-row
@selection-change="handleSelectionChange"
class="custom-table-container"
height="640px"
style="width: 100%; --table-row-height: 32px;"
:header-cell-style="{ backgroundColor: '#f5f7fa' }"
>
<el-table-column
2025-09-17 10:19:20 +08:00
type="index"
prop="id"
label=""
width="45"
:index="getRowIndex"
></el-table-column>
2025-05-20 16:34:38 +08:00
<el-table-column type="selection" width="30"></el-table-column>
<el-table-column prop="jyrq" label="检验日期" v-if="false"></el-table-column>
<el-table-column prop="yq" label="仪器" v-if="false"></el-table-column>
<el-table-column prop="ybh" label="样本号" v-if="false"></el-table-column>
<el-table-column prop="xmdh" label="项目" width="60" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="xmmc" label="名称" width="100" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="csjg" label="结果" width="100" >
<template #default="scope">
2025-09-17 10:19:20 +08:00
<el-input
v-model="scope.row.csjg"
size="small"
class="full-width-input"
:disabled="readonly"
@blur="handleCsjgBlur(scope.row)"
@keyup.enter="handleCsjgEnter(scope.row)"
></el-input>
2025-05-20 16:34:38 +08:00
</template>
</el-table-column>
<el-table-column prop="od" label="OD" width="60" v-if="false">
<template #default="scope">
<el-input v-model="scope.row.od" size="small" class="full-width-input"></el-input>
</template>
</el-table-column>
<el-table-column prop="cutoff" label="SCO" width="60" v-if="false">
<template #default="scope">
2025-09-17 10:19:20 +08:00
<el-input v-model="scope.row.cutoff" size="small" class="full-width-input"></el-input>
2025-05-20 16:34:38 +08:00
</template>
</el-table-column>
<el-table-column prop="refs" label="参考值" width="100" show-overflow-tooltip>
</el-table-column>
2025-09-17 10:19:20 +08:00
<el-table-column prop="dw" label="单位" width="100" show-overflow-tooltip >
2025-05-20 16:34:38 +08:00
</el-table-column>
</el-table>
</div>
</template>
<script setup>
2025-09-17 10:19:20 +08:00
import { ref, toRefs, watch, nextTick, onMounted } from "vue";
import { deleteresult, changeresult } from "@/api/liswork/work/LisWork.js";
import { ElMessageBox, ElMessage } from "element-plus";
// 组件属性定义
2025-05-20 16:34:38 +08:00
const props = defineProps({
2025-09-17 10:19:20 +08:00
tableData: { type: Array, default: () => [] },
tableKey: { type: Array, default: () => [] },
readonly: { type: Boolean, default: false },
hasChanges: { type: Boolean, default: false }
2025-05-20 16:34:38 +08:00
});
2025-09-17 10:19:20 +08:00
// 事件定义
2025-05-20 16:34:38 +08:00
const emits = defineEmits([
2025-09-17 10:19:20 +08:00
'add',
'save',
'delete',
'batch-delete',
'fetchLabResults',
'delfalg',
'update:tableData'
2025-05-20 16:34:38 +08:00
]);
2025-09-17 10:19:20 +08:00
// 解构props
const { tableData, tableKey, readonly, hasChanges } = toRefs(props);
2025-05-20 16:34:38 +08:00
const selectedRows = ref([]);
const filteredList = ref([]);
const originalData = ref(JSON.parse(JSON.stringify(props.tableData)));
2025-09-17 10:19:20 +08:00
const isEnterHandled = ref(false);
const originalCsjgMap = ref({});
const isSaving = ref({}); // 用于防止重复提交的标记
2025-05-20 16:34:38 +08:00
2025-09-17 10:19:20 +08:00
// 组件挂载时初始化缓存
onMounted(() => {
updateOriginalCsjgMap();
});
/**
* 更新csjg原始值缓存
*/
const updateOriginalCsjgMap = () => {
const newMap = {};
props.tableData.forEach((row) => {
const uniqueKey = getRowUniqueKey(row);
let originalCsjg = row.csjg;
originalCsjg = originalCsjg == null ? "" : String(originalCsjg).trim();
newMap[uniqueKey] = originalCsjg;
});
originalCsjgMap.value = newMap;
};
/**
* 获取行的唯一标识
*/
const getRowUniqueKey = (row) => {
// 优先使用id,没有则使用xmdh确保唯一性
return row.id || row.xmdh;
};
/**
* csjg输入框失焦触发保存
*/
const handleCsjgBlur = async (row) => {
if (isEnterHandled.value) return;
await saveCsjgChange(row);
};
/**
* csjg输入框回车触发保存
*/
const handleCsjgEnter = async (row) => {
isEnterHandled.value = true;
await saveCsjgChange(row);
nextTick(() => {
const input = document.activeElement;
if (input.tagName === "INPUT") input.blur();
});
setTimeout(() => {
isEnterHandled.value = false;
}, 100);
};
/**
* 核心:保存csjg修改(调用API + 失败回滚)
*/
const saveCsjgChange = async (row) => {
const uniqueKey = getRowUniqueKey(row);
// 防止重复提交
if (isSaving.value[uniqueKey]) return;
const oldCsjg = originalCsjgMap.value[uniqueKey] || "";
let newCsjg = row.csjg || "";
// 标准化处理
newCsjg = String(newCsjg).trim();
// 调试日志
console.log(`
行${uniqueKey} 比较:
缓存旧值:"${oldCsjg}"(类型:${typeof oldCsjg})
当前新值:"${newCsjg}"(类型:${typeof newCsjg})
是否相等:${oldCsjg === newCsjg}
`);
// 无变化或只读状态,直接返回
if (oldCsjg === newCsjg || readonly.value) {
console.log("值未变化,无需保存");
return;
}
try {
// 设置提交中标记
isSaving.value[uniqueKey] = true;
const requestParams = {
...row,
csjg: newCsjg
};
console.log('提交参数:', requestParams);
changeresult(requestParams).then(response => {
originalCsjgMap.value[uniqueKey] = newCsjg;
emits("update:hasChanges", true);
ElMessage.success("结果保存成功");
}) .catch(error => {
ElMessage.error(error.message || "保存接口调用失败,已恢复原结果");
row.csjg = oldCsjg;
});
} catch (error) {
console.error('保存失败:', error);
ElMessage.error(error.message || "保存接口调用失败,已恢复原结果");
// 回滚值
row.csjg = oldCsjg;
} finally {
// 清除提交中标记
isSaving.value[uniqueKey] = false;
}
};
/**
* 处理选择变化
*/
2025-05-20 16:34:38 +08:00
const handleSelectionChange = (val) => {
selectedRows.value = val;
2025-09-17 10:19:20 +08:00
emits('delfalg', val.length === 0);
2025-05-20 16:34:38 +08:00
};
2025-09-17 10:19:20 +08:00
/**
* 新增行
*/
2025-05-20 16:34:38 +08:00
const handleAdd = () => {
const newRow = {
2025-09-17 10:19:20 +08:00
// 生成临时ID确保唯一性
id: `temp_${Date.now()}`,
jyrq: tableKey.value?.jyrq,
yq: tableKey.value?.yq,
ybh: tableKey.value?.ybh,
csjg: ""
2025-05-20 16:34:38 +08:00
};
2025-09-17 10:19:20 +08:00
2025-05-20 16:34:38 +08:00
tableData.value.push(newRow);
2025-09-17 10:19:20 +08:00
2025-05-20 16:34:38 +08:00
nextTick(() => {
2025-09-17 10:19:20 +08:00
const tableBody = document.querySelector('.el-table__body-wrapper');
if (tableBody) {
tableBody.scrollTop = tableBody.scrollHeight;
2025-05-20 16:34:38 +08:00
}
2025-09-17 10:19:20 +08:00
// 聚焦到新行的输入框
const inputs = document.querySelectorAll('.full-width-input .el-input__inner');
2025-05-20 16:34:38 +08:00
if (inputs.length > 0) {
2025-09-17 10:19:20 +08:00
inputs[inputs.length - 1].focus();
2025-05-20 16:34:38 +08:00
}
2025-09-17 10:19:20 +08:00
updateOriginalCsjgMap();
});
2025-05-20 16:34:38 +08:00
};
2025-09-17 10:19:20 +08:00
// 新增行核心方法:接收父组件传递的项目数据
const addRowFromDict = async (rowData) => {
if (!rowData?.xmdh) {
ElMessage.error("项目编码不能为空");
return;
}
// 1. 校验输入数据有效性
if (!rowData || !rowData.xmdh) {
ElMessage.error("新增失败:项目编码不能为空");
return false;
}
// 2. 检查是否重复添加同一项目(根据xmdh判断)
const isDuplicate = tableData.value.some(item => item.xmdh === rowData.xmdh);
if (isDuplicate) {
ElMessage.warning(`项目【${rowData.xmmc}】已存在`);
return;
}
// 3. 补全新增行的必要字段(如无则初始化)
const newRow = { ...rowData, csjg: "" };
// 4. 添加到表格数据
tableData.value.push(newRow);
console.log("子组件新增行:", newRow);
// 3. emit同步给父组件(更新父组件的labResuts)
emit('update:tableData', [...tableData.value]);
// 4. 同步缓存
await nextTick();
updateOriginalCsjgMap();
2025-05-20 16:34:38 +08:00
2025-09-17 10:19:20 +08:00
// 5. 自动聚焦
nextTick(() => {
const lastInput = document.querySelector(`.el-table__row:last-child .csjg-input .el-input__inner`);
if (lastInput) lastInput.focus();
});
console.log(`成功新增项目行:`, newRow);
return true;
};
/**
* 保存数据
*/
2025-05-20 16:34:38 +08:00
const handleSave = () => {
if (hasChanges.value) {
emits('save', props.tableData);
2025-09-17 10:19:20 +08:00
updateOriginalCsjgMap();
emits("update:hasChanges", false);
2025-05-20 16:34:38 +08:00
}
};
2025-09-17 10:19:20 +08:00
/**
* 删除行
*/
2025-05-20 16:34:38 +08:00
const handleDelete = (row) => {
2025-09-17 10:19:20 +08:00
emits("delete", row);
tableData.value = tableData.value.filter((item) => item.id !== row.id);
updateOriginalCsjgMap();
2025-05-20 16:34:38 +08:00
};
2025-09-17 10:19:20 +08:00
/**
* 批量删除
*/
2025-05-20 16:34:38 +08:00
const handleBatchDelete = async () => {
2025-09-17 10:19:20 +08:00
if (selectedRows.value.length === 0) return;
2025-05-20 16:34:38 +08:00
try {
2025-09-17 10:19:20 +08:00
await ElMessageBox.confirm(
`确定要删除选中的 ${selectedRows.value.length} 条记录吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
2025-05-20 16:34:38 +08:00
2025-09-17 10:19:20 +08:00
await deleteresult(selectedRows.value);
emits('fetchLabResults');
ElMessage.success('删除成功');
2025-05-20 16:34:38 +08:00
} catch (error) {
2025-09-17 10:19:20 +08:00
if (error !== 'cancel') { // 排除用户取消的情况
ElMessage.error('删除失败: ' + (error.message || '未知错误'));
}
2025-05-20 16:34:38 +08:00
}
2025-09-17 10:19:20 +08:00
};
2025-05-20 16:34:38 +08:00
2025-09-17 10:19:20 +08:00
/**
* 重置表格的修改状态
*/
2025-05-20 16:34:38 +08:00
const resetChange = () => {
originalData.value = JSON.parse(JSON.stringify(props.tableData));
2025-09-17 10:19:20 +08:00
updateOriginalCsjgMap();
emits("update:hasChanges", false);
2025-05-20 16:34:38 +08:00
};
2025-09-17 10:19:20 +08:00
/**
* 刷新数据
*/
const fetchLabResults = () => {
emits('fetchLabResults');
};
2025-05-20 16:34:38 +08:00
2025-09-17 10:19:20 +08:00
/**
* 获取行号
*/
2025-05-20 16:34:38 +08:00
const getRowIndex = (index) => {
return index + 1;
};
2025-09-17 10:19:20 +08:00
// 子组件(labresult.vue):修改缓存初始化逻辑,监听tableData非空后再执行
onMounted(() => {
// 若初始tableData为空,等待父组件数据加载后再初始化缓存
if (props.tableData.length > 0) {
updateOriginalCsjgMap();
}
});
// 子组件(labresult.vue):修改watch逻辑
// 增强watch:当tableData从空变为非空时,初始化缓存
watch(
() => props.tableData.length,
(newLength, oldLength) => {
// 只有当tableData从空(0)变为有数据(>0)时,才初始化缓存
if (oldLength === 0 && newLength > 0) {
updateOriginalCsjgMap();
console.log("子组件:父组件数据加载完成,初始化缓存");
}
}
);
2025-05-20 16:34:38 +08:00
// 暴露方法给父组件
defineExpose({
handleAdd,
handleDelete,
handleSave,
2025-09-17 10:19:20 +08:00
handleBatchDelete,
resetChange,
addRowFromDict,
2025-05-20 16:34:38 +08:00
});
</script>
<style scoped>
.custom-table-container /deep/ .el-table__cell {
padding: 0 !important;
}
.custom-table-container /deep/ .el-table__row {
height: var(--table-row-height) !important;
}
.full-width-input /deep/ .el-input__inner {
width: 100%;
2025-09-17 10:19:20 +08:00
height: calc(var(--table-row-height) - 2px);
padding: 0 8px; /* 增加内边距提升输入体验 */
2025-05-20 16:34:38 +08:00
border: 0px solid #dcdfe6;
border-radius: 0;
box-sizing: border-box;
2025-09-17 10:19:20 +08:00
background-color: transparent;
2025-05-20 16:34:38 +08:00
}
.full-width-input /deep/ .el-input__inner:focus {
border-color: #409eff;
2025-09-17 10:19:20 +08:00
box-shadow: 0 0 0 1px rgba(64, 158, 255, 0.2);
2025-05-20 16:34:38 +08:00
outline: none;
}
2025-09-17 10:19:20 +08:00
2025-05-20 16:34:38 +08:00
.el-table__body-wrapper {
2025-09-17 10:19:20 +08:00
overflow: visible !important;
2025-05-20 16:34:38 +08:00
}
.new-row-enter-active {
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
2025-09-17 10:19:20 +08:00
</style>