Compare commits

..

3 Commits

Author SHA1 Message Date
tangw
7e876ce499 Merge branch 'main' of http://47.97.125.165:8902/jiangs/lis8.0-vue3 2025-09-17 10:24:01 +08:00
tangw
9244000fb0 主程序 2025-09-17 10:19:20 +08:00
tangw
02451a912f 返回值定义及处理修改 2025-08-27 11:28:50 +08:00
11 changed files with 2342 additions and 196 deletions

View File

@ -26,25 +26,32 @@ export function getDate() {
})
}
//增加字典数据
export function addLabPatService(data) {
//
export function changeresult(data) {
return request({
url: '/liswork/add',
url: '/lisworkoper/changeresult',
method: 'post',
data
})
}
//修改字典数据
export function updateLabPatService(data) {
//修改病人信息
export function updateLabPat(data) {
return request({
url: '/liswork/update',
url: '/lisworkoper/changepat',
method: 'post',
data
})
}
export function changepatcolumn(query) {
return request({
url: '/lisworkoper/changepatcolumn',
method: 'get',
params:query
})
}
// 查询数据列表
// 查询结果数据列表
export function queryLabResults(query) {
return request({
url: '/liswork/resultinfo',
@ -52,7 +59,22 @@ export function queryLabResults(query) {
params: query
})
}
// 查询检验项目
export function queryXmInfo(query) {
return request({
url: '/liswork/queryxminfo',
method: 'get',
params: query
})
}
// 查询常用取值
export function queryXmVal(query) {
return request({
url: '/liswork/queryxmval',
method: 'get',
params: query
})
}
// 批量删除结果
export function deleteresult(data) {
return request({
@ -71,3 +93,33 @@ export function check2(query) {
})
}
//取消审核
export function uncheck2(query) {
return request({
url: '/lisworkoper/uncheck2',
method: 'get',
params: query
})
}
//取消审核
export function unconfirmlog(query) {
return request({
url: '/lisworkoper/unconfirmlog',
method: 'get',
params: query
})
}
export function checkuser(query) {
return request({
url: '/liswork/checkuserid',
method: 'get',
params: query
})
}
export function reglimit(query) {
return request({
url: '/lisworkoper/reglimit',
method: 'get',
params: query
})
}

View File

@ -1,3 +1,4 @@
import { createVNode, render } from 'vue';
import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import { getToken } from '@/utils/auth'
@ -123,7 +124,7 @@ service.interceptors.response.use(res => {
return Promise.reject(new Error(msg))
} else if (code === 3) {
//提示并成功
ElMessage({ message: msg, type: 'warning' })
ElMessage({ message: msg, type: 'success' })
return Promise.resolve(res.data)
} else if (code === 2) {
//选择是否后续操作
@ -131,6 +132,13 @@ service.interceptors.response.use(res => {
return Promise.resolve(res.data);
})
return Promise.reject('error');
} else if (code === 1) {
//提示并失败
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 4) {
//返回特定值,需要后续处理,表示当前操作需要处理后再调用,problemId是步骤关键参数,需要再次调用时引入
return Promise.resolve(res.data)
} else if (code === 5) {
// 1. 提取后端返回的问题ID(根据实际字段名调整)
const problemId = res.data.problemId;

View File

@ -212,7 +212,6 @@ function handleQueryDetail() {
resetData()
}
//提交表单数据
function submitForm() {
proxy.$refs["dictRef"].validate(valid => {
if (valid) {

View File

@ -0,0 +1,323 @@
<template>
<!-- 遮罩层 -->
<div
v-if="visible"
class="dialog-mask"
@click="handleCancel"
></div>
<!-- 弹窗主体 -->
<div
v-if="visible"
class="dialog-container"
>
<div class="dialog-box">
<!-- 标题区域 -->
<div class="dialog-header">
<h3 class="dialog-title">{{ title }}</h3>
<button
class="dialog-close"
@click="handleCancel"
aria-label="关闭"
>
<span>×</span>
</button>
</div>
<!-- 内容区域 -->
<div class="dialog-body">
<!-- 工号输入 -->
<div class="form-item">
<label class="form-label">用户工号</label>
<input
type="text"
v-model.trim="userId"
class="form-input"
placeholder="请输入用户工号"
@keyup.enter="handleConfirm"
>
</div>
<!-- 密码输入 -->
<div class="form-item">
<label class="form-label">密码</label>
<input
type="password"
v-model="password"
class="form-input"
placeholder="请输入密码"
@keyup.enter="handleConfirm"
>
</div>
<!-- 错误提示 -->
<p v-if="errorMsg" class="error-message">{{ errorMsg }}</p>
</div>
<!-- 按钮区域 -->
<div class="dialog-footer">
<button
class="btn btn-cancel"
@click="handleCancel"
>
取消
</button>
<button
class="btn btn-confirm"
@click="handleConfirm"
:disabled="!userId || !password"
>
确认验证
</button>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref, watch } from 'vue';
// 组件属性
const props = defineProps({
// 控制弹窗显示/隐藏
visible: {
type: Boolean,
default: false
},
// 弹窗标题(由外部参数控制)
title: {
type: String,
default: '用户身份验证'
},
// 验证规则(可选,由外部传入)
validationRules: {
type: Object,
default: () => ({
// 工号最小长度
userIdMinLength: 3,
// 密码最小长度
passwordMinLength: 6
})
}
});
// 组件事件
const emit = defineEmits([
'update:visible', // 控制弹窗显示状态
'onConfirm', // 验证成功回调
'onCancel' // 取消验证回调
]);
// 表单数据
const userId = ref('');
const password = ref('');
const errorMsg = ref('');
// 监听弹窗显示状态,重置表单
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 打开弹窗时重置表单
userId.value = '';
password.value = '';
errorMsg.value = '';
}
}
);
// 处理确认验证
const handleConfirm = () => {
// 基础验证
if (!userId.value) {
errorMsg.value = '请输入用户工号';
return;
}
if (!password.value) {
errorMsg.value = '请输入密码';
return;
}
// 应用外部传入的验证规则
if (userId.value.length < props.validationRules.userIdMinLength) {
errorMsg.value = `工号长度不能少于${props.validationRules.userIdMinLength}位`;
return;
}
if (password.value.length < props.validationRules.passwordMinLength) {
errorMsg.value = `密码长度不能少于${props.validationRules.passwordMinLength}位`;
return;
}
// 验证通过,返回用户工号
emit('onConfirm', {
userId: userId.value,
password: password.value
});
// 关闭弹窗
emit('update:visible', false);
};
// 处理取消
const handleCancel = () => {
emit('onCancel');
emit('update:visible', false);
};
</script>
<style scoped>
/* 遮罩层 */
.dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
transition: opacity 0.3s;
}
/* 弹窗容器 */
.dialog-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
/* 弹窗主体 */
.dialog-box {
width: 100%;
max-width: 400px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
/* 标题区域 */
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
background-color: #f9fafb;
}
.dialog-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.dialog-close {
padding: 0;
background: transparent;
border: none;
font-size: 20px;
color: #999;
cursor: pointer;
transition: color 0.2s;
}
.dialog-close:hover {
color: #333;
}
/* 内容区域 */
.dialog-body {
padding: 24px 20px;
}
.form-item {
margin-bottom: 16px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #666;
}
.form-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: #4096ff;
box-shadow: 0 0 0 2px rgba(64, 150, 255, 0.2);
}
.error-message {
margin: 8px 0 0;
color: #f56c6c;
font-size: 12px;
line-height: 1.5;
}
/* 按钮区域 */
.dialog-footer {
display: flex;
justify-content: flex-end;
padding: 12px 20px;
border-top: 1px solid #f0f0f0;
background-color: #f9fafb;
}
.btn {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.btn-cancel {
margin-right: 10px;
border: 1px solid #dcdfe6;
background-color: #fff;
color: #606266;
}
.btn-cancel:hover {
background-color: #f5f7fa;
border-color: #c0c4cc;
}
.btn-confirm {
border: 1px solid #4096ff;
background-color: #4096ff;
color: #fff;
}
.btn-confirm:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
.btn-confirm:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: #4096ff;
border-color: #4096ff;
}
</style>

View File

@ -0,0 +1,331 @@
<template>
<!-- 遮罩层 -->
<div
v-if="visible"
class="dialog-mask"
@click="handleCancel"
></div>
<!-- 弹窗主体 -->
<div
v-if="visible"
class="dialog-container"
>
<div class="dialog-box">
<!-- 标题区域 -->
<div class="dialog-header">
<h3 class="dialog-title">{{ title }}</h3>
<button
class="dialog-close"
@click="handleCancel"
aria-label="关闭"
>
<span>×</span>
</button>
</div>
<!-- 内容区域 -->
<div class="dialog-body">
<!-- 搜索框 -->
<el-input
v-model="searchKey"
placeholder="搜索(支持名称、编码、简拼)..."
class="search-input"
clearable
@clear="filterDictData"
@input="filterDictData"
/>
<!-- 字典列表 -->
<el-table
:data="filteredDictData"
height="300px"
border
@row-click="selectItem"
@row-dblclick="selectItem"
:loading="isDictLoading"
>
<el-table-column prop="value" label="编码" width="100" />
<el-table-column prop="label" label="名称" width="200" />
<el-table-column prop="pinyin" label="简拼" width="150" />
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button size="small" type="primary" @click="selectItem(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { getFirstLetter } from '@/api/pinyin.js'; // 拼音处理工具
// 组件参数
const props = defineProps({
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '选择字典项'
},
modelValue: {
type: [String, Number],
default: ''
},
dictType: {
type: String,
required: true
},
placeholder: {
type: String,
default: '请输入或双击选择'
},
clearable: {
type: Boolean,
default: true
},
fetchDict: {
type: Function,
required: true
},
disabled: {
type: Boolean,
default: false
}
});
// 组件事件
const emit = defineEmits([
'update:modelValue',
'update:labelValue',
'select',
'update:visible',
'onCancel'
]);
// 内部状态管理
const tempValue = ref('');
const labelValue = ref('');
const dictData = ref([]); // 存储带简拼的字典数据
const filteredDictData = ref([]);
const isDictLoading = ref(true);
const searchKey = ref('');
const dialogVisible = ref(props.visible); // 内部弹窗状态
// 显示值计算属性
const displayValue = computed({
get() {
return labelValue.value || tempValue.value || props.modelValue || '';
},
set(newValue) {
tempValue.value = newValue;
if (labelValue.value && newValue !== labelValue.value) {
labelValue.value = '';
emit('update:labelValue', '');
}
}
});
// 加载字典数据并添加简拼字段
const loadDictData = async () => {
try {
isDictLoading.value = true;
const rawData = await props.fetchDict(props.dictType);
// 为每个字典项添加简拼字段
dictData.value = (rawData || []).map(item => ({
...item,
pinyin: getFirstLetter(item.label) || ''
}));
filteredDictData.value = [...dictData.value];
syncLabelWithValue();
} catch (error) {
console.error(`加载${props.dictType}字典失败:`, error);
dictData.value = [];
filteredDictData.value = [];
} finally {
isDictLoading.value = false;
}
};
// 根据当前值同步显示标签
const syncLabelWithValue = () => {
if (props.modelValue) {
const matched = dictData.value.find(
item => String(item.value) === String(props.modelValue)
);
labelValue.value = matched?.label || props.modelValue;
emit('update:labelValue', labelValue.value);
}
};
// 字典搜索过滤逻辑
const filterDictData = () => {
const key = searchKey.value.trim().toLowerCase();
if (!key) {
filteredDictData.value = [...dictData.value];
return;
}
filteredDictData.value = dictData.value.filter(item => {
const matchLabel = item.label?.toLowerCase().includes(key) || false;
const matchValue = String(item.value).toLowerCase().includes(key);
const matchPinyin = item.pinyin?.toLowerCase().includes(key) || false;
return matchLabel || matchValue || matchPinyin;
});
};
// 打开弹窗
const openDictSelector = () => {
if (!props.disabled && !isDictLoading.value) {
dialogVisible.value = true;
searchKey.value = '';
filterDictData();
}
};
// 选择字典项
const selectItem = (item) => {
if (!item) return;
emit('update:modelValue', item.value);
emit('update:labelValue', item.label);
emit('select', item);
tempValue.value = item.value;
labelValue.value = item.label;
dialogVisible.value = false;
emit('update:visible', false);
};
// 处理取消
const handleCancel = () => {
emit('onCancel');
dialogVisible.value = false;
emit('update:visible', false);
};
// 监听visible属性变化
watch(() => props.visible, (newVal) => {
dialogVisible.value = newVal;
if (newVal) {
searchKey.value = '';
filterDictData();
}
});
// 监听初始值变化
watch(() => props.modelValue, (newVal) => {
tempValue.value = newVal || '';
if (newVal && !isDictLoading.value) {
syncLabelWithValue();
}
}, { immediate: true });
// 监听字典类型变化,重新加载数据
watch(() => props.dictType, loadDictData, { immediate: false });
// 组件挂载时加载字典
onMounted(async () => {
await loadDictData();
});
</script>
<style scoped>
/* 遮罩层 */
.dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
transition: opacity 0.3s;
}
/* 弹窗容器 */
.dialog-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
/* 弹窗主体 */
.dialog-box {
width: 100%;
max-width: 600px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
/* 标题区域 */
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
background-color: #f9fafb;
}
.dialog-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.dialog-close {
padding: 0;
background: transparent;
border: none;
font-size: 20px;
color: #999;
cursor: pointer;
transition: color 0.2s;
}
.dialog-close:hover {
color: #333;
}
/* 内容区域 */
.dialog-body {
padding: 20px;
}
.search-input {
margin-bottom: 16px;
}
/* 表格样式优化 */
::v-deep .el-table {
border-radius: 4px;
}
::v-deep .el-table__empty-text {
color: #909399;
}
/* 按钮样式优化 */
::v-deep .el-button--small {
padding: 4px 12px;
}
</style>

View File

@ -0,0 +1,229 @@
<template>
<div class="dict-input-wrapper">
<el-input
v-model="displayValue"
:placeholder="placeholder"
:clearable="clearable"
:disabled="isDictLoading || disabled"
@blur="handleBlur"
@clear="handleClear"
@dblclick="handleDblClick"
>
<template #prefix>
<el-icon v-if="isDictLoading" size="16"><Loading /></el-icon>
</template>
<template #suffix>
<el-icon @click="openDictSelector" size="16" class="select-icon">
<ArrowDown />
</el-icon>
</template>
</el-input>
<!-- 字典选择弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
@close="handleDialogClose"
>
<el-input
v-model="searchKey"
placeholder="搜索(支持名称、编码、简拼)..."
class="mb-4"
clearable
@clear="filterDictData"
@input="filterDictData"
/>
<el-table
:data="filteredDictData"
height="300px"
border
@row-click="selectItem"
@row-dblclick="selectItem"
>
<el-table-column prop="value" label="编码" width="100" />
<el-table-column prop="label" label="名称" width="200" />
<el-table-column prop="pinyin" label="简拼" width="150" /> <!-- 显示简拼便于调试 -->
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button size="small" type="primary" @click="selectItem(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { Loading, ArrowDown } from '@element-plus/icons-vue';
import { getFirstLetter } from '@/api/pinyin.js'; // 引入拼音处理工具
// 组件参数
const props = defineProps({
modelValue: { type: [String, Number], default: '' },
dictType: { type: String, required: true },
placeholder: { type: String, default: '请输入或双击选择' },
clearable: { type: Boolean, default: true },
disabled: {type: Boolean, default: false},
fetchDict: {type: Function, required: true},
dialogTitle: {type: String, default: '选择字典项'},
loadOnMount: { type: Boolean, default: true }
});
// 组件事件
const emit = defineEmits(['update:modelValue', 'update:labelValue', 'select']);
// 内部状态
const tempValue = ref('');
const labelValue = ref('');
const dictData = ref([]); // 存储带简拼的字典数据
const filteredDictData = ref([]);
const isDictLoading = ref(false);
const dialogVisible = ref(false);
const searchKey = ref('');
// 显示值计算
const displayValue = computed({
get() {
return labelValue.value || tempValue.value || props.modelValue || '';
},
set(newValue) {
tempValue.value = newValue;
if (labelValue.value && newValue !== labelValue.value) {
labelValue.value = '';
emit('update:labelValue', '');
}
}
});
// 加载字典数据(并添加简拼字段)
const loadDictData = async () => {
try {
isDictLoading.value = true;
const rawData = await props.fetchDict(props.dictType);
// 为每个字典项添加简拼字段
dictData.value = (rawData || []).map(item => ({
...item,
pinyin: getFirstLetter(item.label) // 新增pinyin字段存储简拼
}));
filteredDictData.value = [...dictData.value];
} catch (error) {
console.error(`加载${props.dictType}字典失败:`, error);
dictData.value = [];
filteredDictData.value = [];
} finally {
isDictLoading.value = false;
}
};
// 核心搜索逻辑(支持模糊搜索+简拼搜索)
const filterDictData = () => {
const key = searchKey.value.trim().toLowerCase();
if (!key) {
filteredDictData.value = [...dictData.value];
return;
}
filteredDictData.value = dictData.value.filter(item => {
// 1. 模糊搜索:匹配label(名称)或value(编码)
const matchLabel = item.label.toLowerCase().includes(key);
const matchValue = String(item.value).toLowerCase().includes(key);
// 2. 简拼搜索:匹配首字母简拼
const matchPinyin = item.pinyin.toLowerCase().includes(key);
// 满足任一条件即匹配
return matchLabel || matchValue || matchPinyin;
});
};
// 双击事件处理
const handleDblClick = () => {
if (!props.disabled && !isDictLoading.value) {
dialogVisible.value = true;
}
};
// 关键修改:打开弹窗时重新加载数据
const openDictSelector = async () => {
if (props.disabled || isDictLoading.value) {
return;
}
// 打开弹窗前先加载数据(确保每次打开都是最新的)
await loadDictData();
// 数据加载完成后再显示弹窗
dialogVisible.value = true;
searchKey.value = '';
filterDictData();
};
// 选择字典项
const selectItem = (item) => {
emit('update:modelValue', item.value);
emit('update:labelValue', item.label);
emit('select', item);
tempValue.value = item.value;
labelValue.value = item.label;
dialogVisible.value = false;
};
// 其他方法(失焦、清空等)保持不变
const handleBlur = () => { /* ... */
};
const handleClear = () => { /* ... */
};
const handleDialogClose = () => {
dialogVisible.value = false;
};
// 监听初始值变化
watch(() => props.modelValue, (newVal) => {
tempValue.value = newVal || '';
if (newVal && !isDictLoading.value) {
const matched = dictData.value.find(item => String(item.value) === String(newVal));
labelValue.value = matched?.label || newVal;
emit('update:labelValue', labelValue.value);
}
}, {immediate: true});
// 组件挂载时加载字典
onMounted(async () => {
if (props.loadOnMount) { // 仅当loadOnMount为true时(默认),才在挂载时加载
await loadDictData();
}
});
defineExpose({
openDictSelector, // 暴露打开弹窗的方法
// 可选:暴露其他可能需要的方法(如刷新字典数据)
loadDictData
});
</script>
<style scoped>
/* 样式保持不变 */
.dict-input-wrapper {
position: relative;
}
.select-icon {
cursor: pointer;
color: #666;
margin-left: 5px;
}
.select-icon:hover {
color: #409eff;
}
:deep(.el-input__suffix) {
gap: 4px;
}
</style>

View File

@ -4,62 +4,110 @@
<el-form :model="labPat" label-width="100px" class="compact-form" label-position="left">
<el-form-item label="病人来源" label-width="70px">
<el-col :span="16">
<dict-input v-model="labPat.brly" :dict-type="'PT'" placeholder="请输入病人来源" :fetch-dict="fetchDict" @update:labelValue="(label) => labPat.brlyLabel = label" dialog-title="选择病人来源"/>
<dict-input v-model="labPat.brly" :dict-type="'PT'" placeholder="请输入病人来源" :fetch-dict="fetchDict"
@update:labelValue="(label) => labPat.brlyLabel = label" dialog-title="选择病人来源"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
@select="handleDictSelect"
/>
</el-col>
<el-col :span="8">
<el-switch v-model="labPat.jzbz" active-text="急诊" active-color="#FD0101"></el-switch>
<el-switch v-model="jzbzSwitch" active-text="急诊" active-color="#FD0101" @change="handleFieldChange"></el-switch>
</el-col>
</el-form-item>
<el-form-item label="病 历 号" label-width="70px">
<el-input v-model="labPat.brdh" placeholder="请输入病历号"></el-input>
<el-input v-model="labPat.brdh" placeholder="请输入病历号"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"></el-input>
</el-form-item>
<el-form-item label="姓 名" label-width="70px">
<el-input v-model="labPat.brxm" placeholder="请输入姓名"></el-input>
<el-input v-model="labPat.brxm" placeholder="请输入姓名"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"></el-input>
</el-form-item>
<el-form-item label="性 别" label-width="70px">
<el-col :span="8">
<dict-input v-model="labPat.brxb" placeholder="性别" :dict-type="'SX'" :fetch-dict="fetchDict" @update:labelValue="(label) => labPat.brxbLabel = label"/>
<dict-input v-model="labPat.brxb" placeholder="性别" :dict-type="'SX'" :fetch-dict="fetchDict"
@update:labelValue="(label) => labPat.brxbLabel = label"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
@select="handleDictSelect"
/>
</el-col>
<el-col :span="4"><div>年龄</div></el-col>
<el-col :span="6">
<el-input v-model="labPat.nl" placeholder="年龄"></el-input>
<el-input v-model="labPat.nl" placeholder="年龄"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"></el-input>
</el-col>
<el-col :span="6">
<dict-input v-model="labPat.nldw" placeholder="年月" :dict-type="'AU'" :fetch-dict="fetchDict" @update:labelValue="(label) => labPat.nldwLabel = label"/>
<dict-input v-model="labPat.nldw" placeholder="年月" :dict-type="'AU'" :fetch-dict="fetchDict"
@update:labelValue="(label) => labPat.nldwLabel = label"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
@select="handleDictSelect"
/>
</el-col>
</el-form-item>
<el-form-item label="送检科室" label-width="70px">
<dict-input v-model="labPat.ksdh" placeholder="请输入科室" :dict-type="'DP'" :fetch-dict="fetchDict" @update:labelValue="(label) => labPat.ksdhLabel = label"/>
<dict-input v-model="labPat.ksdh" placeholder="请输入科室" :dict-type="'DP'" :fetch-dict="fetchDict"
@update:labelValue="(label) => labPat.ksdhLabel = label"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
@select="handleDictSelect"
/>
</el-form-item>
<el-form-item label="床 号" label-width="70px">
<el-input v-model="labPat.ch" placeholder="请输入床号"></el-input>
<el-input v-model="labPat.ch" placeholder="请输入床号"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
></el-input>
</el-form-item>
<el-form-item label="样本类型" label-width="70px">
<dict-input v-model="labPat.yblx" placeholder="请输入样本类型" :dict-type="'BT'" :fetch-dict="fetchDict" @update:labelValue="(label) => labPat.yblxLabel = label"/>
<dict-input v-model="labPat.yblx" placeholder="请输入样本类型" :dict-type="'BT'" :fetch-dict="fetchDict"
@update:labelValue="(label) => labPat.yblxLabel = label"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
@select="handleDictSelect"
/>
</el-form-item>
<el-form-item label="申请医生" label-width="70px">
<dict-input v-model="labPat.sjys" placeholder="请输入申请医生" :dict-type="'SRD'" :fetch-dict="fetchDict" @update:labelValue="(label) => labPat.sjysLabel = label"/>
<dict-input v-model="labPat.sjys" placeholder="请输入申请医生" :dict-type="'SRD'" :fetch-dict="fetchDict"
@update:labelValue="(label) => labPat.sjysLabel = label"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
@select="handleDictSelect"
/>
</el-form-item>
<el-form-item label="上机时间" label-width="70px">
<el-date-picker v-model="labPat.sqsj" type="datetime" placeholder="上机时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"></el-date-picker>
<el-date-picker v-model="labPat.sqsj" type="datetime" placeholder="上机时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
></el-date-picker>
</el-form-item>
<el-form-item label="采样时间" label-width="70px">
<el-date-picker v-model="labPat.cyrq" type="datetime" placeholder="采样时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"></el-date-picker>
<el-date-picker v-model="labPat.cyrq" type="datetime" placeholder="采样时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
></el-date-picker>
</el-form-item>
<el-form-item label="报告时间" label-width="70px">
<el-date-picker v-model="labPat.dysj" type="datetime" placeholder="报告时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"></el-date-picker>
<el-date-picker v-model="labPat.dysj" type="datetime" placeholder="报告时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
@blur="handleFieldChange"
@keyup.enter="handleFieldChange"
></el-date-picker>
</el-form-item>
<el-form-item label="临床诊断" label-width="70px">
<el-input v-model="labPat.zd" placeholder="请输入临床诊断"></el-input>
<el-input v-model="labPat.zd" placeholder="请输入临床诊断" @blur="handleFieldChange" @keyup.enter="handleFieldChange"></el-input>
</el-form-item>
<el-form-item label="备 注" label-width="70px">
<el-input v-model="labPat.bz" placeholder="请输入备注"></el-input>
<el-input v-model="labPat.bz" placeholder="请输入备注" @blur="handleFieldChange" @keyup.enter="handleFieldChange"></el-input>
</el-form-item>
<el-form-item label="检验医生" label-width="70px">
<el-input v-model="labPat.yhdh" placeholder="请输入检验医生"></el-input>
<el-input v-model="labPat.yhdh" placeholder="请输入检验医生" @blur="handleFieldChange" @keyup.enter="handleFieldChange"></el-input>
</el-form-item>
<el-form-item label="审核医生" label-width="70px">
<el-input v-model="labPat.hdys" placeholder="请输入审核医生"></el-input>
<el-input v-model="labPat.hdys" placeholder="请输入审核医生" @blur="handleFieldChange" @keyup.enter="handleFieldChange"></el-input>
</el-form-item>
<el-form-item label="审核状态" >
<el-radio-group v-model="labPat.jgbz">
@ -79,6 +127,8 @@
<script setup>
import DictInput from './DictInput.vue';
import {changepatcolumn, updateLabPat} from "@/api/liswork/work/LisWork.js";
const loading = ref(true);
const props = defineProps({
labPat: {
type: Object,
@ -90,41 +140,123 @@ const props = defineProps({
default: () => []
}
});
// 计算属性:将布尔值转换为 0/1 绑定到 labPat.jzbz
const jzbzSwitch = computed({
get() {
// 后端返回的 0/1 转换为布尔值(供开关显示)
return props.labPat.jzbz == "1";
},
set(checked) {
// 开关的 true/false 转换为 1/0 存到实体中
props.labPat.jzbz = checked ? "1" : "0";
}
});
const emit = defineEmits(['update:labPat']);
// 通过inject获取全局方法
const comDict = inject('comDict');
const lastValue = ref(JSON.stringify(props.labPat));
const prevFieldState = ref(null); // 记录当前修改的字段及旧值 { field: '字段名', oldValue: 旧值 }
/**
* 通用字段变化处理(失焦、回车触发)
*/
const handleFieldChange = async () => {
await triggerApiUpdate();
};
// 保存方法
const saveMain = () => {
// 提交时可获取原始值(brly)和标签(brlyLabel)
console.log('提交数据:', {
rawValue: props.labPat.brly,
label: props.labPat.brlyLabel,
...props.labPat
/**
* 字典选择完成处理(弹窗选择后触发)
*/
const handleDictSelect = async () => {
// 字典选择后直接触发更新
await triggerApiUpdate();
};
/**
* 对比新旧对象,找出修改的字段及旧值
* @param oldObj 旧对象
* @param newObj 新对象
* @returns { field: string, oldValue: any } | null
*/
const getChangedField = (oldObj, newObj) => {
const oldKeys = Object.keys(oldObj);
for (const key of oldKeys) {
if (newObj[key] !== oldObj[key]) {
return { field: key, oldValue: oldObj[key], newValue: newObj[key] };
}
}
return null;
};
/**
* 触发API更新的核心方法
*/
const triggerApiUpdate = async () => {
const currentLabPat = toRaw(props.labPat);
const currentValue = JSON.stringify(currentLabPat);
const oldValue = lastValue.value;
// 只有值发生变化时才调用API
if (currentValue !== lastValue.value) {
// 2. 找出修改的字段及旧值(用于回滚)
const oldObj = JSON.parse(oldValue);
const changedField = getChangedField(oldObj, currentLabPat);
prevFieldState.value = changedField; // 记录修改前的状态
console.log('prevFieldState:', prevFieldState.value)
console.log('labPatKey:', props.labPatKey)
try {
loading.value = true;
changepatcolumn({...props.labPatKey,column:changedField?.field,value:changedField?.newValue}).then(response => {
console.log('response:', response)
console.log('表单更新成功');
lastValue.value = currentValue;
loading.value = false;
}) .catch(error => {
console.error('表单更新失败', error);
loading.value = false;
if (prevFieldState.value) {
const { field, oldValue,newValue } = prevFieldState.value;
// 回滚字段值(通过emit触发父组件更新,避免直接修改props)
emit('update:labPat', {
...toRaw(props.labPat),
[field]: oldValue
});
// 实际项目中调用接口提交数据
} else {
// 若未找到具体字段,整体回滚
emit('update:labPat', JSON.parse(oldValue));
}
// 回滚后更新缓存
lastValue.value = oldValue;
});
} catch (error) {
console.error('更新接口调用失败:', error);
}finally {
loading.value = false;
prevFieldState.value = null; // 清空临时记录
}
}
};
// 手动保存
const saveMain = async () => {
await triggerApiUpdate();
};
// 重置方法
const resetMain = () => {
emit('update:labPat', {
const resetData = {
...props.labPat,
brly: '',
brlyLabel: '',
ksdh: '',
ksdhLabel: '',
sjys: '',
sjysLabel: '',
brxb: '',
brxbLabel: '',
nldw: '',
nldwLabel: '',
yblx: '',
yblxLabel: '',
// 保留其他字段的默认值
...props.labPat,
jzbz: props.labPat.jzbz || 0,
jgbz: props.labPat.jgbz || 0
});
}
jzbz: 0,
jgbz: 0
};
emit('update:labPat', resetData);
lastValue.value = JSON.stringify(resetData);
};
// 字典获取方法(适配子组件)
const fetchDict = async (dictType) => {
const dictRefs = await comDict(dictType);

View File

@ -12,8 +12,12 @@
:header-cell-style="{ backgroundColor: '#f5f7fa' }"
>
<el-table-column
type="index" prop="id" label="" width="45" :index="getRowIndex" >
</el-table-column>
type="index"
prop="id"
label=""
width="45"
:index="getRowIndex"
></el-table-column>
<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>
@ -25,7 +29,14 @@
</el-table-column>
<el-table-column prop="csjg" label="结果" width="100" >
<template #default="scope">
<el-input v-model="scope.row.csjg" size="small" class="full-width-input"></el-input>
<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>
</template>
</el-table-column>
<el-table-column prop="od" label="OD" width="60" v-if="false">
@ -35,114 +46,265 @@
</el-table-column>
<el-table-column prop="cutoff" label="SCO" width="60" v-if="false">
<template #default="scope">
<el-input v-model="scope.row.od" size="small" class="full-width-input"></el-input>
<el-input v-model="scope.row.cutoff" size="small" class="full-width-input"></el-input>
</template>
</el-table-column>
<el-table-column prop="refs" label="参考值" width="100" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="dw" label="单位" width="100" show-overflow-tooltip >
</el-table-column>
</el-table>
</div>
</template>
<script setup>
//import {ref, reactive, watch, toRefs, nextTick} from 'vue';
import {deleteresult, queryLabResults} from "@/api/liswork/work/LisWork.js";
import {delType} from "@/api/system/dict/type.js";
import {ElMessageBox} from "element-plus";
const { proxy } = getCurrentInstance();
import { ref, toRefs, watch, nextTick, onMounted } from "vue";
import { deleteresult, changeresult } from "@/api/liswork/work/LisWork.js";
import { ElMessageBox, ElMessage } from "element-plus";
// 组件属性定义
const props = defineProps({
// 表格数据
tableData: { type: Array, default: () => [] },
//主键
tableKey: { type: Array, default: () => [] },
// 部门选项
// departments: {
// type: Array,
// default: () => []
// },
// 是否只读
readonly: {type: Boolean, default: false}
readonly: { type: Boolean, default: false },
hasChanges: { type: Boolean, default: false }
});
const { tableData, tableKey, readonly,undeleteflag,hasChanges } = toRefs(props);
// 事件定义
const emits = defineEmits([
'add', // 新增行
'save', // 保存数据
'delete', // 删除行
'batch-delete', // 批量删除
'fetchLabResults' //刷新数据
'add',
'save',
'delete',
'batch-delete',
'fetchLabResults',
'delfalg',
'update:tableData'
]);
// 选中的行
// 解构props
const { tableData, tableKey, readonly, hasChanges } = toRefs(props);
const selectedRows = ref([]);
const filteredList = ref([]);
// 原始数据副本,用于检测变化
const originalData = ref(JSON.parse(JSON.stringify(props.tableData)));
// 处理选择变化
const isEnterHandled = ref(false);
const originalCsjgMap = ref({});
const isSaving = ref({}); // 用于防止重复提交的标记
// 组件挂载时初始化缓存
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;
}
};
/**
* 处理选择变化
*/
const handleSelectionChange = (val) => {
selectedRows.value = val;
if (val.length === 0) {
emits('delfalg', true);
}else{
emits('delfalg', false);
}
emits('delfalg', val.length === 0);
};
// 新增行
/**
* 新增行
*/
const handleAdd = () => {
// emits('add');
// 创建新行数据
const newRow = {
jyrq: tableKey.jyrq,
yq: tableKey.yq,
ybh: tableKey.ybh
// 生成临时ID确保唯一性
id: `temp_${Date.now()}`,
jyrq: tableKey.value?.jyrq,
yq: tableKey.value?.yq,
ybh: tableKey.value?.ybh,
csjg: ""
};
// 添加到表格数据
tableData.value.push(newRow);
// 动画结束后移除标记
// 可选:滚动到表格底部并聚焦到第一个输入框
nextTick(() => {
const tableData = document.querySelector('.el-table__body-wrapper');
if (tableData) {
tableData.scrollTop = tableData.scrollHeight;
const tableBody = document.querySelector('.el-table__body-wrapper');
if (tableBody) {
tableBody.scrollTop = tableBody.scrollHeight;
}
// 聚焦到新行的第一个输入框
const inputs = document.querySelectorAll('.el-input__inner');
// 聚焦到新行的输入框
const inputs = document.querySelectorAll('.full-width-input .el-input__inner');
if (inputs.length > 0) {
inputs[4].focus(); // 通常最后一个是操作列的按钮,所以取倒数第二个
inputs[inputs.length - 1].focus();
}
})
};
// 保存数据
updateOriginalCsjgMap();
});
};
// 新增行核心方法:接收父组件传递的项目数据
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();
// 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;
};
/**
* 保存数据
*/
const handleSave = () => {
if (hasChanges.value) {
emits('save', props.tableData);
// 保存后更新原始数据
// originalData.value = JSON.parse(JSON.stringify(props.tableData));
hasChanges.value = false;
updateOriginalCsjgMap();
emits("update:hasChanges", false);
}
};
// 删除行
/**
* 删除行
*/
const handleDelete = (row) => {
emits('delete', row);
tableData.value = tableData.value.filter(item => item.id !== row.id);
emits("delete", row);
tableData.value = tableData.value.filter((item) => item.id !== row.id);
updateOriginalCsjgMap();
};
// 批量删除(调用API)
/**
* 批量删除
*/
const handleBatchDelete = async () => {
if (selectedRows.value.length === 0) return;
try {
if (selectedRows.length === 0) {
return;
}
// 确认删除
await ElMessageBox.confirm(
`确定要删除选中的 ${selectedRows.value.length} 条记录吗?`,
'提示',
@ -154,78 +316,95 @@ const handleBatchDelete = async () => {
);
await deleteresult(selectedRows.value);
fetchLabResults();
emits('fetchLabResults');
ElMessage.success('删除成功');
} catch (error) {
// 错误已经在拦截器中处理,这里可以不做处理或添加额外逻辑
console.error('删除失败:', error);
if (error !== 'cancel') { // 排除用户取消的情况
ElMessage.error('删除失败: ' + (error.message || '未知错误'));
}
}
// 重置表格的修改状态
const resetChange = () => {
// 原始数据副本,用于检测变化
originalData.value = JSON.parse(JSON.stringify(props.tableData));
hasChanges.value = false;
};
/**
* 重置表格的修改状态
*/
const resetChange = () => {
originalData.value = JSON.parse(JSON.stringify(props.tableData));
updateOriginalCsjgMap();
emits("update:hasChanges", false);
};
/**
* 刷新数据
*/
const fetchLabResults = () => {
emits('fetchLabResults');
}
// 监听数据变化
watch(() => props.tableData, (newVal, oldVal) => {
// 简单比较数据是否有变化
//hasChanges.value = JSON.stringify(newVal) !== JSON.stringify(originalData.value);
}, { deep: true });
};
// 获取行号(支持分页时显示正确行号)
/**
* 获取行号
*/
const getRowIndex = (index) => {
// 如果有分页,可以根据当前页码计算真实行号
// return (currentPage - 1) * pageSize + index + 1;
// 无分页时直接返回索引+1
return index + 1;
};
// 子组件(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("子组件:父组件数据加载完成,初始化缓存");
}
}
);
// 暴露方法给父组件
defineExpose({
handleAdd,
handleDelete,
handleSave,
handleBatchDelete
handleBatchDelete,
resetChange,
addRowFromDict,
});
</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%;
height: calc(var(--table-row-height) - 2px); /* 减去上下边框 */
padding: 0 0px; /* 减小内边距 */
height: calc(var(--table-row-height) - 2px);
padding: 0 8px; /* 增加内边距提升输入体验 */
border: 0px solid #dcdfe6;
border-radius: 0;
box-sizing: border-box;
background-color: transparent; /* 可选:透明背景 */
background-color: transparent;
}
/* 输入框聚焦效果 */
.full-width-input /deep/ .el-input__inner:focus {
border-color: #409eff;
box-shadow: 0 0 0 0px rgba(64, 158, 255, 0.2);
box-shadow: 0 0 0 1px rgba(64, 158, 255, 0.2);
outline: none;
}
/* 新增行动画 */
.el-table__body-wrapper {
overflow: visible !important; /* 确保动画不被裁剪 */
overflow: visible !important;
}
.new-row-enter-active {
@ -236,5 +415,4 @@ defineExpose({
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>

View File

@ -0,0 +1,186 @@
<template>
<!-- 遮罩层 -->
<div
v-if="visible"
class="dialog-mask"
@click="handleMaskClick"
></div>
<!-- 弹窗主体 -->
<div
v-if="visible"
class="dialog-wrapper"
>
<div class="dialog-container">
<!-- 标题 -->
<div class="dialog-header">
<h3>操作原因</h3>
<button class="close-btn" @click="handleCancel">×</button>
</div>
<!-- 内容区 -->
<div class="dialog-body">
<p class="reason-text">{{ reasonText }}</p>
<textarea
v-model="inputValue"
class="reason-input"
placeholder="请输入操作原因..."
rows="4"
></textarea>
</div>
<!-- 按钮区 -->
<div class="dialog-footer">
<button class="btn cancel" @click="handleCancel">取消</button>
<button class="btn confirm" @click="handleConfirm">确认</button>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref } from 'vue';
// 组件属性
const props = defineProps({
visible: {
type: Boolean,
default: false
},
reasonText: {
type: String,
default: '请填写操作原因'
}
});
// 组件事件
const emit = defineEmits(['update:visible', 'onConfirm', 'onCancel']);
// 输入框内容
const inputValue = ref('');
// 确认按钮点击
const handleConfirm = () => {
emit('onConfirm', inputValue.value); // 传递输入值
emit('update:visible', false);
inputValue.value = ''; // 重置输入
};
// 取消按钮点击
const handleCancel = () => {
emit('onCancel');
emit('update:visible', false);
inputValue.value = ''; // 重置输入
};
// 遮罩层点击
const handleMaskClick = () => {
handleCancel();
};
</script>
<style scoped>
/* 基础样式与之前保持一致,确保按钮边框和输入框正常 */
.dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.dialog-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.dialog-container {
width: 100%;
max-width: 500px;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #eee;
}
.dialog-header h3 {
margin: 0;
font-size: 16px;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 20px;
color: #999;
cursor: pointer;
}
.dialog-body {
padding: 20px;
}
.reason-text {
margin: 0 0 15px 0;
color: #666;
}
.reason-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
box-sizing: border-box;
}
.reason-input:focus {
outline: none;
border-color: #4096ff;
}
.dialog-footer {
padding: 15px 20px;
border-top: 1px solid #eee;
text-align: right;
}
.btn {
padding: 6px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.cancel {
margin-right: 10px;
border: 1px solid #ddd;
background: #fff;
color: #666;
}
.confirm {
border: 1px solid #4096ff;
background: #4096ff;
color: #fff;
}
</style>

View File

@ -0,0 +1,340 @@
<template>
<!-- 遮罩层 -->
<div
v-if="visible"
class="dialog-mask"
@click="handleCancel"
></div>
<!-- 弹窗主体 -->
<div
v-if="visible"
class="dialog-container"
>
<div class="dialog-box">
<!-- 标题区域 -->
<div class="dialog-header">
<h3 class="dialog-title">危急值结果上报</h3>
<button
class="dialog-close"
@click="handleCancel"
aria-label="关闭"
>
<span>×</span>
</button>
</div>
<!-- 内容区域 -->
<div class="dialog-body">
<div class="red-static-text">{{ msgdata }}</div>
<el-col :span="1">
<el-form-item label="上报方式" prop="uploadTp">
<el-radio-group v-model="uploadTp" size="default">
<el-radio v-for="(item, index) in uploadtypeptions" :key="index" :label="item.value"
:disabled="item.disabled">{{item.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<div class="form-item" v-if="uploadTp === 2">
<label class="form-label">电话通知人</label>
<input
type="text"
v-model.trim="userId"
class="form-input"
placeholder="请输入医生工号"
@keyup.enter="handleupload"
>
</div>
<div class="form-item" v-if="uploadTp === 2">
<label class="form-label">联系电话</label>
<input
type="text"
v-model="phone"
class="form-input"
placeholder="请输入联系电话"
@keyup.enter="handleupload"
>
</div>
<!-- 错误提示 -->
<p v-if="errorMsg" class="error-message">{{ errorMsg }}</p>
</div>
<!-- 按钮区域 -->
<div class="dialog-footer">
<button
class="btn btn-cancel"
@click="handleCancel"
>
取消
</button>
<button
class="btn btn-upload"
@click="handleupload"
:disabled="isSubmitDisabled"
>
确认上报
</button>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits, ref, watch } from 'vue';
const uploadtypeptions = ref([{
"label": "系统通知",
"value": 1
}, {
"label": "电话通知",
"value": 2
}])
// 组件属性
const props = defineProps({
// 控制弹窗显示/隐藏
visible: {
type: Boolean,
default: false
},
// 弹窗内容
msgdata: {
type: String,
default: ''
},
// 显示规则
showRules: {
type: Number,
default:1
}
});
// 组件事件
const emit = defineEmits([
'update:visible', // 控制弹窗显示状态
'onupload', // 验证成功回调
'onCancel' // 取消验证回调
]);
// 表单数据
const uploadTp = ref('1');
const userId = ref('');
const phone = ref('');
const errorMsg = ref('');
// 监听弹窗显示状态,重置表单
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 打开弹窗时重置表单
uploadTp.value = 1; // 打开弹窗时默认选中1
userId.value = '';
phone.value = '';
errorMsg.value = '';
}
}
);
const isSubmitDisabled = computed(() => {
// 系统通知(1):无需其他字段,直接可提交
if (uploadTp.value === 1) {
return false;
}
// 电话通知(2):需填写通知人和电话
return !userId.value || !phone.value;
});
// 处理确认验证
const handleupload = () => {
errorMsg.value = ''; // 清空之前的错误提示
// 基础验证
if(uploadTp.value===2) {
if (!userId.value) {
errorMsg.value = '请输入用户工号';
return;
}
if(props.showRules===3) {
if (!phone.value) {
errorMsg.value = '请输入电话';
return;
}
}
}
// 验证通过,返回用户工号
emit('onupload', {
userId: userId.value,
phone: phone.value,
uploadTp:uploadTp.value
});
// 关闭弹窗
emit('update:visible', false);
};
// 处理取消
const handleCancel = () => {
emit('onCancel');
emit('update:visible', false);
};
</script>
<style scoped>
/* 遮罩层 */
.dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
transition: opacity 0.3s;
}
/* 弹窗容器 */
.dialog-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
/* 弹窗主体 */
.dialog-box {
width: 100%;
max-width: 400px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
/* 标题区域 */
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
background-color: #f9fafb;
}
.dialog-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #333;
}
.dialog-close {
padding: 0;
background: transparent;
border: none;
font-size: 20px;
color: #999;
cursor: pointer;
transition: color 0.2s;
}
.dialog-close:hover {
color: #333;
}
/* 内容区域 */
.dialog-body {
padding: 24px 20px;
}
.form-item {
margin-bottom: 16px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #666;
}
.form-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: #4096ff;
box-shadow: 0 0 0 2px rgba(64, 150, 255, 0.2);
}
.error-message {
margin: 8px 0 0;
color: #f56c6c;
font-size: 12px;
line-height: 1.5;
}
/* 按钮区域 */
.dialog-footer {
display: flex;
justify-content: flex-end;
padding: 12px 20px;
border-top: 1px solid #f0f0f0;
background-color: #f9fafb;
}
.btn {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.btn-cancel {
margin-right: 10px;
border: 1px solid #dcdfe6;
background-color: #fff;
color: #606266;
}
.btn-cancel:hover {
background-color: #f5f7fa;
border-color: #c0c4cc;
}
.btn-upload {
border: 1px solid #4096ff;
background-color: #4096ff;
color: #fff;
}
.btn-upload:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
.btn-upload:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: #4096ff;
border-color: #4096ff;
}
.red-static-text {
color: red; /* 字体颜色设为红色 */
white-space: pre-wrap; /* 保留空格和换行(可选,配合 pre 标签或手动换行时使用) */
line-height: 1.6; /* 行高,增强多行可读性 */
/* 可选:添加其他样式 */
font-size: 14px;
margin: 10px 0;
}
</style>

View File

@ -11,7 +11,7 @@
<el-button type="success" plain icon="Edit" :disabled="changeResultFlag" @click="handleUpdate" v-hasPermi="['system:dict:edit']">保存</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" @click="handleCheck" v-hasPermi="['system:dict:import']">初报</el-button>
<el-button type="success" plain icon="Edit" @click="handleCheck0" v-hasPermi="['system:dict:import']">初报</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Download" @click="handleCheck" v-hasPermi="['system:dict:import']">初审</el-button>
@ -23,7 +23,7 @@
<el-button type="success" plain icon="Download" @click="handleCheck" v-hasPermi="['system:dict:import']">审核</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleUnCheck" v-hasPermi="['system:dict:export']">取消审核</el-button>
<el-button type="success" plain icon="Upload" @click="handleUnCheck" v-hasPermi="['system:dict:export']">取消审核</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Edit" @click="handleUnCheck" v-hasPermi="['system:dict:export']">预览</el-button>
@ -77,14 +77,16 @@
<el-row :gutter="5">
<el-col :span="5" :xs="24">
<div class="compact-form">
<LabPatForm v-model:labPat="labPat" ref="labPatRef" @save="savelabPat" @reset="resetlabPat" />
<LabPatForm v-model:labPat="labPat" :labPatKey="queryParams" ref="labPatRef" @save="savelabPat" @reset="resetlabPat" />
</div>
</el-col>
<el-col :span="10" :xs="24">
<div class="compact-form">
<LabResultForm ref="labResultRef"
<LabResultForm
ref="labResultRef"
:tableData="labResuts"
:tableKey="queryParams"
:is-data-refreshed="isDataRefreshed"
@add="addLabResult"
@delete="deleteLabResults"
@edit="editLabResult"
@ -106,7 +108,41 @@
/>
</div>
</el-col>
<ItemInput
ref="itemDictRef"
v-model="selectedItemValue"
:label-value.sync="selectedItemLabel"
:dict-type="'LAB_ITEM'"
:fetch-dict="fetchLabItemDict"
:dialog-title="'选择检验项目'"
:disabled="false"
:load-on-mount="false"
:table-data.sync="labResuts"
class="hidden-dict-input"
@select="handleItemSelect"
/>
</el-row>
<!-- 引入弹窗组件 -->
<unconfirmlog
v-model:visible="dialogVisible"
:reason-text="currentReason"
@onConfirm="unconfirmlogConfirm"
@onCancel="unconfirmlogCancel"
/>
<CheckUser
v-model:visible="showCheckUser"
:title="checktitle"
:validation-rules="{ userIdMinLength: 4, passwordMinLength: 6 }"
@onConfirm="handleAuthSuccess"
@onCancel="handleAuthCancel"
/>
<uploadwarning
v-model:visible="showuploadwarning"
:msgdata="warningMsgdata"
:showRules=1
@onupload="handleWarningSuccess"
@onCancel="handleWarningCancel"
/>
</div>
</template>
@ -115,17 +151,25 @@ import {
getDate,
queryLabPatList,
queryLabResults,
updateLabPatService,
updateLabPat,
queryLabPat,
check2
check2, uncheck2, unconfirmlog, checkuser, reglimit, queryXmInfo
} from "../../../api/liswork/work/LisWork.js";
import {querylabInstrList,querylabInstrListByLisgroup} from "../../../api/liswork/dict/LabInstr.js";
import {getComDicts, queryComDictListService} from "../../../api/liswork/dict/ComDict.js";
import LabPatForm from './components/labpat.vue'
import LabResultForm from './components/labresult.vue'
import LabPatListForm from './components/labpatlist.vue'
import Unconfirmlog from './components/unconfirmlog.vue'
import Ddlb from './components/ddlb.vue'
import CheckUser from './components/CheckUser.vue'
import uploadwarning from './components/uploadwarning.vue'
import UCKDictInput from './components/DictInput.vue';
import {ElMessage, ElMessageBox} from "element-plus";
import ItemInput from "@/views/liswork/work/components/ItemInput.vue";
const showuploadwarning = ref(false);
const single = ref(true);
const delResultFlag = ref(true);
const changeResultFlag = ref(true);
@ -173,6 +217,10 @@ const setInstr=ref('')
const yq0=ref('')
const jyrq0=ref('')
const ybh0=ref('')
const checktitle=ref('操作人身份验证')
const nextstepname=ref('')
const warningMsgdata=ref('')
const checkuserid=ref('')
const querydata = reactive({
form: {},
queryParams: {
@ -182,7 +230,9 @@ const querydata = reactive({
yq: '1',
ybh: '1',
days: 1,
instrGroup:'#'
instrGroup:'#',
problemId:0,
yhdh:''
},
rules: {
ybh: [],
@ -192,12 +242,119 @@ const querydata = reactive({
}
,
});
// 父组件:新增“数据刷新标记”,初始为false
const isDataRefreshed = ref(false);
// 新增:控制标记的重置(避免持续触发子组件缓存更新)
const resetDataRefreshFlag = () => {
// 延迟重置,确保子组件有足够时间接收标记并更新缓存
setTimeout(() => {
isDataRefreshed.value = false;
}, 100);
};
const { queryParams, form, rules } = toRefs(querydata);
//子组件labResult.vue
const labResultRef = ref(null);
//子组件labPatRef.vue
const labPatRef = ref(null);
// 弹窗控制状态
const dialogVisible = ref(false);
const showCheckUser = ref(false);
// 弹窗显示的原因文本
const currentReason = ref('');
// 存储需要传递给API的参数
const apiParams = ref(null);
// 处理弹窗确认(获取返回值后调用API)
const unconfirmlogConfirm = async (inputValue) => {
try {
console.log('用户输入的操作原因:', inputValue);
// 调用API,携带用户输入的原因和之前保存的参数
loading.value = true;
unconfirmlog({...queryParams.value,reason: inputValue}).then(response => {
console.log('response:', response)
loading.value = false;
if(response.code=="0") {
runnextstepname();
}
}) .catch(error => {
console.error('审核失败', error);
loading.value = false;
});
// API调用成功后的处理
console.log('操作成功:', response);
// 可以添加成功提示、刷新数据等逻辑
} catch (error) {
console.error('操作失败:', error);
// 错误处理
} finally {
// 重置参数
apiParams.value = null;
}
};
// 处理弹窗取消
const unconfirmlogCancel = () => {
console.log('用户取消了操作');
// 重置参数
apiParams.value = null;
cancalnextstepname();
};
// 处理验证成功
const handleAuthSuccess = async (userInfo) => {
console.log('验证信息:', userInfo);
// 调用后端接口进行实际验证
loading.value = true;
checkuser({yhdh: userInfo.userId,mm: userInfo.password}).then(response => {
console.log('response:', response)
loading.value = false;
console.log('userInfo:',userInfo);
if(response.code=="0") {
checkuserid.value=userInfo.userId;
runnextstepname();
}
}) .catch(error => {
console.error('验证接口调用失败', error);
loading.value = false;
checkuserid.value='';
});
};
// 处理取消验证
const handleAuthCancel = () => {
console.log('用户取消了验证');
// 执行取消后的操作
cancalnextstepname();
};
// 处理危急值登记
const handleWarningSuccess = async (userInfo) => {
console.log('处理危急值登记:', userInfo);
// 调用后端接口进行实际验证
loading.value = true;
reglimit({...queryParams.value,qrr: userInfo.userId,tzfs: userInfo.uploadTp,phone:userInfo.phone}).then(response => {
console.log('危急值response:', response.code);
loading.value = false;
if(response.code=="0") {
runnextstepname();
}
if(response.code=="4") {
ElMessageBox.confirm(response.msg, '系统提示', { confirmButtonText: '是', cancelButtonText: '否', type: 'warning' }).then(() => {
queryParams.value.problemId=queryParams.value.problemId+1;
handleWarningSuccess(userInfo);
}).catch(() => {
runnextstepname();
});
}
}) .catch(error => {
console.error('处理危急值登记接口调用失败', error);
loading.value = false;
});
};
// 处理取消验证
const handleWarningCancel = () => {
console.log('用户取消了登记');
// 执行取消后的操作
cancalnextstepname();
};
// 页面载入时获取基本默认值参数信息,后面有扩展需要都放这里
const loadYqConfig = async () => {
// 使用 .value 获取响应式数据的值
@ -313,6 +470,10 @@ const fetchLabResults = async () => {
// console.log('RESULT:', response);
labResuts.value = response.data;
loading.value = false;
// 关键:设置“数据刷新标记”为true(删除后数据变化,需更新子组件缓存)
isDataRefreshed.value = true;
// 数据刷新后重置标记
resetDataRefreshFlag();
}) .catch(error => {
console.error('列表数据加载失败', error);
loading.value = false;
@ -382,11 +543,14 @@ const selectJob = async (job) => {
queryParams.value.jyrq = job.jyrq;
queryParams.value.yq = job.yq;
queryParams.value.ybh = job.ybh;
checkQuery()
isDataRefreshed.value = true;
//await nextTick();
// 获取对应的明细数据
await fetchLabPat();
await fetchLabResults();
resetDataRefreshFlag();
}
//样本列表检索时刷新列表信息
const searchJobs = async (text, page, size) => {
@ -427,7 +591,8 @@ const setchangeResultFlag = (value) =>{
}
//新增
const handleAdd = () => {
labResultRef.value?.handleAdd();
// labResultRef.value?.handleAdd();
openItemDictDialog();
};
function handleUpdate(){
}
@ -436,20 +601,77 @@ function handleDelete(){
}
function loadingPatLabel(){
// labPatRef.value?.loadingLabel();
}
function handleCheck0(){
}
function handleCheck(){
queryParams.value.problemId=0;
nextstepname.value='handleCheck2';
queryParams.value.yhdh=checkuserid.value
if(queryParams.value.yhdh=='') {
checktitle.value='审核人工号密码验证'
showCheckUser.value = true;
}else {
handleCheck2();
}
}
function handleCheck2(){
loading.value = true;
console.log('queryParams:', queryParams.value)
queryParams.value.yhdh=checkuserid.value
check2(queryParams.value).then(response => {
console.log('response:', response)
loading.value = false;
if(response.code=="4"&& response.problemId===4) {
console.log('queryParams1111:', queryParams.value)
nextstepname.value='handleCheck2';
queryParams.value.problemId = response.problemId;
// 保存需要传递给API的参数
apiParams.value = queryParams;
// 设置弹窗显示的原因文本
warningMsgdata.value = response.msg;
// 显示弹窗
showuploadwarning.value = true;
}
console.log('response:', response)
}).catch(error => {
console.error('审核失败', error);
loading.value = false;
});
}
function handleUnCheck(){
queryParams.value.problemId=0;
nextstepname.value='handleuncheck2';
queryParams.value.yhdh=checkuserid.value
if(queryParams.value.yhdh=='') {
checktitle.value='取消审核人工号密码验证'
showCheckUser.value = true;
}else {
handleuncheck2();
}
}
function handleuncheck2(){
loading.value = true;
queryParams.value.yhdh=checkuserid.value
uncheck2(queryParams.value).then(response => {
loading.value = false;
if(response.code=="4") {
nextstepname.value='handleuncheck2';
queryParams.value.problemId = response.problemId;
// 保存需要传递给API的参数
apiParams.value = queryParams;
// 设置弹窗显示的原因文本
currentReason.value = response.msg;
// 显示弹窗
dialogVisible.value = true;
}
}) .catch(error => {
console.error('解除审核失败', error);
loading.value = false;
});
}
function handlePrint(){
}
@ -462,13 +684,17 @@ const handleQuery= async()=>{
await loadlabPatList();
//暂时默认打开1号样本,后面由参数控制
querydata.queryParams.ybh='1';
isDataRefreshed.value = true;
await handleQueryYbh();
resetDataRefreshFlag();
}
//切换样本号时载入
const handleQueryYbh = async()=>{
if (checkQuery()===false) return false
isDataRefreshed.value = true;
await fetchLabPat();
await fetchLabResults();
resetDataRefreshFlag();
}
//主键空置拦截
function checkQuery(){
@ -488,7 +714,137 @@ function checkQuery(){
yq0.value=querydata.queryParams.yq
ybh0.value=querydata.queryParams.ybh
}
function runnextstepname(){
if(nextstepname.value!=''){
switch(nextstepname.value) {
case 'handleuncheck2':
handleuncheck2();
break;
case 'handleCheck2':
handleCheck2();
break;
default:
// 当所有case都不匹配时执行(可选)
}
}
}
function cancalnextstepname(){
if(nextstepname.value!=''){
switch(nextstepname.value) {
case 'handleuncheck2':
ElMessage({ message: '取消操作,撤销审核失败!', type: 'error' })
break;
case 'handleCheck2':
ElMessage({ message: '取消操作,审核失败!', type: 'error' })
break;
default:
// 当所有case都不匹配时执行(可选)
}
}
}
// -------------------------- 项目选择相关变量 --------------------------
const itemDictRef = ref(null); // DictInput组件引用
const selectedItemValue = ref(""); // 选择的项目编码xmdh
const selectedItemLabel = ref(""); // 选择的项目名称xmmc
// -------------------------- 1. 项目字典API调用方法(传递给DictInput) --------------------------
/**
* 项目字典加载方法:适配DictInput的fetchDict参数格式
* @param {String} dictType - 父组件传递的dict-type(无实际意义,可忽略)
* @returns {Promise<Array>} - 项目字典数据(格式需匹配DictInput要求:{value, label})
*/
// 父组件中:完善的项目字典加载方法
const fetchLabItemDict = async (dictType) => {
// 日志1:确认方法被调用
try {
// 1. 验证必要参数(如当前仪器yq)
if (!queryParams.value.yq) {
console.error("[fetchLabItemDict] 缺少必要参数:yq(仪器)");
ElMessage.error("请先选择仪器");
return [];
}
// 日志2:打印请求参数
const requestParams = {
pageNum: 1,
pageSize: 1000, // 加载足够多的项目
yq: queryParams.value.yq, // 当前选中的仪器
};
// 2. 调用API(确保queryXmInfo是正确的API方法)
const response = await queryXmInfo(requestParams);
// 日志3:打印原始响应
// 3. 验证响应格式
if (!response) {
throw new Error("API返回为空");
}
if (response.code !== "0") { // 假设0为成功状态码
throw new Error(`API返回错误:${response.msg || "未知错误"}`);
}
if (!Array.isArray(response.data)) {
throw new Error(`API返回data不是数组,实际为:${typeof response.data}`);
}
// 4. 格式化数据(适配DictInput的{value, label}结构)
const formattedData = response.data.map((item, index) => {
// 验证项目数据完整性
if (!item.xmdh) {
console.warn(`[fetchLabItemDict] 第${index}条数据缺少xmdh(项目编码):`, item);
}
if (!item.xmmc) {
console.warn(`[fetchLabItemDict] 第${index}条数据缺少xmmc(项目名称):`, item);
}
return {
value: item.xmdh || `未知编码_${index}`, // 确保value存在
label: item.xmmc || `未知名称_${index}`, // 确保label存在
dw: item.dw || "", // 单位(可选)
refs: item.refs || "", // 参考值(可选)
};
});
// 日志4:打印格式化后的数据量
return formattedData;
} catch (error) {
// 日志5:捕获并显示错误
console.error(`[fetchLabItemDict] 加载失败:`, error.message);
ElMessage.error(`项目字典加载失败:${error.message}`);
return []; // 失败时返回空数组,避免弹窗报错
}
};
// -------------------------- 2. 打开项目选择弹窗 --------------------------
const openItemDictDialog = () => {
if (itemDictRef.value && typeof itemDictRef.value.openDictSelector === 'function') {
itemDictRef.value.openDictSelector(); // 确保方法存在再调用
} else {
console.warn("DictInput组件未加载完成或方法未暴露");
}
};
// -------------------------- 3. 处理项目选择结果:传递给labresult子组件新增行 --------------------------
const handleItemSelect = async (selectedItem) => {
// 1. 构造新增行数据
const newRow = {
id: `temp_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`,
xmdh: selectedItem.value,
xmmc: selectedItem.label,
dw: selectedItem.dw || "",
refs: selectedItem.refs || "",
jyrq: queryParams.value.jyrq,
yq: queryParams.value.yq,
ybh: queryParams.value.ybh,
};
// 2. 调用子组件方法新增行(仅一次)
if (labResultRef.value && typeof labResultRef.value.addRowFromDict === 'function') {
await labResultRef.value.addRowFromDict(newRow);
ElMessage.success(`已新增项目:${newRow.xmmc}`);
}
};
//打开页面需要先初始化的内容都放这里
onMounted(() => {
loadYqConfig();
@ -497,6 +853,7 @@ onMounted(() => {
loadlabPatList();
})
</script>
<style scoped lang="scss">
@ -519,4 +876,15 @@ onMounted(() => {
color: blue;
}
.hidden-dict-input {
/* 定位到屏幕左侧外,视觉上不可见 */
position: absolute;
left: -9999px;
top: auto;
/* 防止占用布局空间 */
width: 1px;
height: 1px;
/* 防止意外聚焦 */
outline: none;
}
</style>