721 lines
22 KiB
Vue
Raw Normal View History

<template>
<div class="app-container">
<div class="table-header">
<div>
项目检索: <el-input v-model="queryParams.sfxmdh" @blur="handleQuery" @keyup.enter="handleQuery" auto-focus
style="width: 200px;margin-right: 10px;" />
<el-button type="primary" @click="handleQuery" class="add-btn"> 查询 </el-button>
<el-button type="primary" @click="addNewItem" class="add-btn"> 新增 </el-button>
<el-button type="success" @click="save" class="add-btn"> 保存 </el-button>
</div>
</div>
<el-table :data="tableData" border style="width: 100%" @cell-click="handleCellClick" :cell-style="cellStyle"
:row-key="(row: any) => row.id" height="calc(100vh - 260px)" highlight-current-row ref="tableRef">
<!-- 收费项目代号 -->
<el-table-column prop="sfxmdh" label="收费项目代号" width="110">
<template #default="scope">
<template v-if="isEditing(scope.row, 'sfxmdh')">
<el-input v-model="scope.row.sfxmdh" size="small" @blur="handleSave(scope.row, 'sfxmdh')"
2025-09-12 11:06:51 +08:00
@keyup.enter="handleSave(scope.row, 'sfxmdh')" auto-focus :ref="getInputRef(scope.row.id, 'sfxmdh')" />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'sfxmdh' })">
{{ scope.row.sfxmdh }}
</span>
</template>
</template>
</el-table-column>
<!-- 收费项目名称 -->
<el-table-column prop="sfxmmc" label="收费项目名称" width="150">
<template #default="scope">
<template v-if="isEditing(scope.row, 'sfxmmc')">
<el-input v-model="scope.row.sfxmmc" size="small" @blur="handleSave(scope.row, 'sfxmmc')"
2025-09-12 11:06:51 +08:00
@keyup.enter="handleSave(scope.row, 'sfxmmc')" auto-focus :ref="getInputRef(scope.row.id, 'sfxmmc')" />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'sfxmmc' })">
{{ scope.row.sfxmmc }}
</span>
</template>
</template>
</el-table-column>
<!-- 简称 -->
<el-table-column prop="bz" label="简称" width="120">
<template #default="scope">
<template v-if="isEditing(scope.row, 'bz')">
<el-input v-model="scope.row.bz" size="small" @blur="handleSave(scope.row, 'bz')"
2025-09-12 11:06:51 +08:00
@keyup.enter="handleSave(scope.row, 'bz')" auto-focus :ref="getInputRef(scope.row.id, 'bz')" />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'bz' })">
{{ scope.row.bz }}
</span>
</template>
</template>
</el-table-column>
<!-- 价格 -->
<el-table-column prop="dj" label="价格" width="150" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'dj')">
<el-input-number v-model="scope.row.dj" :min="1" :max="100" size="small" @blur="handleSave(scope.row, 'dj')"
2025-09-12 11:06:51 +08:00
@keyup.enter="handleSave(scope.row, 'dj')" auto-focus :ref="getInputRef(scope.row.id, 'dj')" />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'dj' })">
{{ scope.row.dj }}
</span>
</template>
</template>
</el-table-column>
<!-- 规格 -->
<el-table-column prop="spec" label="规格" width="120">
<template #default="scope">
<template v-if="isEditing(scope.row, 'spec')">
<el-input v-model="scope.row.spec" size="small" @blur="handleSave(scope.row, 'spec')"
2025-09-12 11:06:51 +08:00
@keyup.enter="handleSave(scope.row, 'spec')" auto-focus :ref="getInputRef(scope.row.id, 'spec')" />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'spec' })">
{{ scope.row.spec }}
</span>
</template>
</template>
</el-table-column>
<!-- 科室 -->
<el-table-column prop="dept" label="科室" width="150">
<template #default="scope">
<template v-if="isEditing(scope.row, 'dept')">
<el-select v-model="scope.row.dept" size="small" @change="handleSave(scope.row, 'dept')"
@blur="handleSave(scope.row, 'dept')" auto-focus filterable style="width: 100%;">
<el-option v-for="item in dictData.DP" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'dept' })">
{{ formatDict(scope.row.dept, 'DP') }}
</span>
</template>
</template>
</el-table-column>
<!-- 采集样本 -->
<el-table-column prop="yblx" label="采集样本" width="140">
<template #default="scope">
<template v-if="isEditing(scope.row, 'yblx')">
<el-select v-model="scope.row.yblx" size="small" @change="handleSave(scope.row, 'yblx')"
@blur="handleSave(scope.row, 'yblx')" auto-focus filterable>
<el-option v-for="item in dictData.BT" :key="item.label" :label="item.label" :value="item.label" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'yblx' })">
{{ scope.row.yblx }}
</span>
</template>
</template>
</el-table-column>
<!-- 报告天数 -->
<el-table-column prop="bgts" label="报告天数" width="120" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'bgts')">
<el-input-number v-model="scope.row.bgts" size="small" @blur="handleSave(scope.row, 'bgts')"
2025-09-12 11:06:51 +08:00
@keyup.enter="handleSave(scope.row, 'bgts')" auto-focus :ref="getInputRef(scope.row.id, 'bgts')" />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'bgts' })">
{{ scope.row.bgts }}
</span>
</template>
</template>
</el-table-column>
<!-- 助记符 -->
<el-table-column prop="zjf" label="助记符" width="150" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'zjf')">
<el-input v-model="scope.row.zjf" size="small" @blur="handleSave(scope.row, 'zjf')"
2025-09-12 11:06:51 +08:00
@keyup.enter.ctrl="handleSave(scope.row, 'zjf')" auto-focus :ref="getInputRef(scope.row.id, 'zjf')" />
</template>
<template v-else>
<div class="multi-line-text" @click.stop="handleCellClick(scope.row, { property: 'zjf' })">
{{ scope.row.zjf }}
</div>
</template>
</template>
</el-table-column>
<!-- 报告领取规则 -->
<el-table-column prop="bglqdh" label="报告领取规则" width="160">
<template #default="scope">
<template v-if="isEditing(scope.row, 'bglqdh')">
<el-select v-model="scope.row.bglqdh" size="small" @change="handleSave(scope.row, 'bglqdh')" filterable
@blur="handleSave(scope.row, 'bglqdh')" auto-focus>
<el-option v-for="item in reportRuleOptions" :key="item.bglqdh" :label="item.bglqmc"
:value="item.bglqdh" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'bglqdh' })">
{{ getOptionLabel(reportRuleOptions, scope.row.bglqdh) }}
</span>
</template>
</template>
</el-table-column>
<!-- 条码分类 -->
<el-table-column prop="sflbmc" label="条码分类" width="130">
<template #default="scope">
<!-- <template v-if="isEditing(scope.row, 'sflbmc')">
<el-select v-model="scope.row.sflbmc" size="small" @change="handleSave(scope.row, 'sflbmc')"
@blur="handleSave(scope.row, 'sflbmc')" auto-focus>
<el-option v-for="item in sflbmcOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template> -->
{{ scope.row.sflbmc }}
</template>
</el-table-column>
<!-- 专业组(样本数) -->
<el-table-column prop="lisgroup1" label="专业组(样本数)" width="140" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'lisgroup1')">
<el-select v-model="scope.row.lisgroup1" size="small" @change="handleSave(scope.row, 'lisgroup1')"
@blur="handleSave(scope.row, 'lisgroup1')" auto-focus filterable>
<el-option v-for="item in dictData.LISGROUP1" :key="item.label" :label="item.label" :value="item.label" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'lisgroup1' })">
{{ scope.row.lisgroup1 }}
</span>
</template>
</template>
</el-table-column>
<!-- 专业组(周转时间) -->
<el-table-column prop="lisgroup2" label="专业组(周转时间)" width="160" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'lisgroup2')">
<el-select v-model="scope.row.lisgroup2" size="small" @change="handleSave(scope.row, 'lisgroup2')"
@blur="handleSave(scope.row, 'lisgroup2')" auto-focus filterable>
<el-option v-for="item in dictData.LISGROUP2" :key="item.label" :label="item.label" :value="item.label" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'lisgroup2' })">
{{ scope.row.lisgroup2 }}
</span>
</template>
</template>
</el-table-column>
<!-- 适应症 -->
<el-table-column prop="indication" label="适应症" width="220">
<template #default="scope">
<template v-if="isEditing(scope.row, 'indication')">
<el-input v-model="scope.row.indication" size="small" type="textarea" :rows="2"
@blur="handleSave(scope.row, 'indication')" @keyup.enter.ctrl="handleSave(scope.row, 'indication')"
2025-09-12 11:06:51 +08:00
auto-focus :ref="getInputRef(scope.row.id, 'indication')" />
</template>
<template v-else>
<div class="multi-line-text" @click.stop="handleCellClick(scope.row, { property: 'indication' })">
{{ scope.row.indication }}
</div>
</template>
</template>
</el-table-column>
<!-- 作用 -->
<el-table-column prop="affect" label="作用" width="220">
<template #default="scope">
<template v-if="isEditing(scope.row, 'affect')">
<el-input v-model="scope.row.affect" size="small" type="textarea" :rows="2"
2025-09-12 11:06:51 +08:00
@blur="handleSave(scope.row, 'affect')" @keyup.enter.ctrl="handleSave(scope.row, 'affect')" auto-focus
:ref="getInputRef(scope.row.id, 'affect')" />
</template>
<template v-else>
<div class="multi-line-text" @click.stop="handleCellClick(scope.row, { property: 'affect' })">
{{ scope.row.affect }}
</div>
</template>
</template>
</el-table-column>
<!-- 采集要求 -->
<el-table-column prop="attention" label="采集要求" width="220">
<template #default="scope">
<template v-if="isEditing(scope.row, 'attention')">
<el-input v-model="scope.row.attention" size="small" type="textarea" :rows="2"
@blur="handleSave(scope.row, 'attention')" @keyup.enter.ctrl="handleSave(scope.row, 'attention')"
2025-09-12 11:06:51 +08:00
auto-focus :ref="getInputRef(scope.row.id, 'attention')" />
</template>
<template v-else>
<div class="multi-line-text" @click.stop="handleCellClick(scope.row, { property: 'attention' })">
{{ scope.row.attention }}
</div>
</template>
</template>
</el-table-column>
<!-- 操作列 -->
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick, onMounted, toRaw, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { xmfeeList, rptgetrules, xmfeeadd, xmfeeUpdate, xmfeeDel } from '@/api/mzcx/index';
import { classCom } from '@/utils/classCom';
// @ts-ignore
import { comDict } from '@/utils/dict'
const total = ref(0);
const deptOptions = [
{ label: '全部', value: '' }
]
const queryParams = ref({
sfxmdh: '',
pageNum: 1,
pageSize: 10,
yljg: 1
})
const reportRuleOptions = ref<{ bglqdh: string, bglqmc: string, bz: string }[]>([]);
interface tableDataItem {
id: string,
sfxmdh: string,
sfxmmc: string,
bz: string,
dj: string,
dept: string,
yblx: string,
zjf: string,
spec: string,
bgts: string,
sflbmc: string,
bglqdh: number,
lisgroup1: string,
lisgroup2: string,
indication: string,
affect: string,
attention: string,
originalData: object,
flag: boolean,
status: boolean,
yljg: number
}
// 表格数据
const tableData = ref<tableDataItem[]>([]);
const inputRefs = ref<Record<string, any>>({});
// 表格引用
const tableRef = ref(null);
const editingState = ref({ rowId: '', field: '' });
// 设置输入框引用
const getInputRef = (rowId: string, field: string) => {
return (el: any) => {
if (el) {
inputRefs.value[`${rowId}-${field}`] = el;
}
};
};
// 判断是否处于编辑状态
const isEditing = (row: any, field: string) => {
return editingState.value && editingState.value.rowId === row.id && editingState.value.field === field;
};
// 获取选项标签
const getOptionLabel = (options: any, value: string) => {
const option = options.find((item: any) => item.bglqdh === value);
return option ? option.bglqmc : '';
};
// 处理单元格点击 - 核心修复部分
const handleCellClick = (row: any, column: any) => {
// 忽略操作列
if (column.label === '操作') return;
const field = column.property;
// 如果点击的是当前编辑的单元格,不重复处理
if (isEditing(row, field)) {
return;
}
// 保存之前编辑的内容
if (editingState.value) {
const prevRow = tableData.value.find((r: any) => r.id === editingState.value.rowId);
if (prevRow) {
handleSave(prevRow, editingState.value.field);
}
}
// 保存原始值用于恢复
row.originalData[field] = JSON.parse(JSON.stringify(row[field]));
// 设置当前编辑状态
editingState.value = {
rowId: row.id,
field: field
};
// 强制刷新UI后聚焦
nextTick(() => {
const inputKey = `${row.id}-${field}`;
2025-09-12 11:06:51 +08:00
const inputComponent = inputRefs.value[inputKey];
if (!inputComponent) return;
// 不同组件的聚焦方式不同,做适配处理
if (inputComponent.focus) {
// 基础输入组件直接调用focus方法
inputComponent.focus();
} else if (inputComponent.$el) {
// 查找内部输入元素
const inputEl = inputComponent.$el.querySelector('input, textarea') ||
inputComponent.$el.querySelector('.el-select__input') ||
inputComponent.$el;
inputEl?.focus?.();
}
});
2025-09-12 11:06:51 +08:00
};
// 保存编辑
const handleSave = (row: any, field: string) => {
// 非编辑状态不处理
if (!isEditing(row, field)) return;
let isValid = true;
let errorMessage = '';
// 字段验证
switch (field) {
case 'sfxmdh':
if (!row[field]?.trim()) {
isValid = false;
errorMessage = '收费项目代号不能为空';
}
break;
// case 'sfxmmc':
// if (!row[field]?.trim()) {
// isValid = false;
// errorMessage = '收费项目名称不能为空';
// }
// break;
// case 'bz':
// if (!row[field]?.trim()) {
// isValid = false;
// errorMessage = '简称不能为空';
// }
// break;
}
// 验证失败处理
if (!isValid) {
row[field] = row.originalData[field];
ElMessage.error(errorMessage);
// 保持编辑状态以便修正
return;
}
// 对比原始数据判断是否有变化
if (!row.status) {
row.status = JSON.stringify(row[field]) !== JSON.stringify(row.originalData[field]);
}
// 清除编辑状态
editingState.value = { rowId: '', field: '' };
console.log('originalData==>', row);
};
interface OperateLists {
addList: any[];
updateList: any[];
}
// 保存
const save = () => {
const { addList, updateList } = tableData.value.reduce<OperateLists>((acc, item) => {
const { originalData, id, status, flag, ...restData } = item;
const submitData = { ...restData, };
if (status) {
flag ? acc.addList.push(submitData) : acc.updateList.push(submitData);
}
return acc;
}, { addList: [], updateList: [] });
console.log('新增列表:', addList);
console.log('更新列表:', updateList);
if (addList.length > 0) {
xmfeeadd(addList).then((res: any) => {
if (res.code == 0) {
ElMessage.success(res.msg);
getList()
}
})
}
if (updateList.length > 0) {
xmfeeUpdate(updateList).then((res: any) => {
if (res.code == 0) {
ElMessage.success(res.msg);
getList()
}
})
}
}
// 单元格样式
const cellClassName = ({ row, column }: { row: any, column: any }) => {
return isEditing(row, column.property)
? 'cell-editing'
: 'cell-editable';
};
// 单元格样式
const cellStyle = ({ row, column, rowIndex, columnIndex }: {
row: any;
column: any;
rowIndex: number;
columnIndex: number;
}) => {
if (column.label == "条码分类") {
const bgColor = classCom.decimalToHexColor(row.bkcolor);
const textColor = classCom.getContrastTextColor(bgColor);
return {
backgroundColor: `${bgColor} !important`,
color: textColor,
};
}
};
// 新增项
const addNewItem = () => {
const newId = Array.from({ length: 14 }, () => Math.floor(Math.random() * 10)).join('');
tableData.value.unshift({
id: newId,
sfxmdh: `NEW${newId}`,
sfxmmc: 'test',
bz: '',
dj: '',
spec: '',
dept: '',
yblx: '',
bgts: '',
zjf: '',
bglqdh: 1,
sflbmc: '',
indication: '',
affect: '',
attention: '',
lisgroup1: '',
lisgroup2: '',
originalData: {},
flag: true,
yljg: 1,
status: true,
});
// ElMessage.info('已添加新类别,请完善信息');
};
// 删除项
const handleDelete = (row: any) => {
ElMessageBox.confirm(`确定删除该${row.sfxmdh}吗?`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
beforeClose: (action, instance, done) => {
if (action === "confirm") {
xmfeeDel({ sfxmdh: row.sfxmdh }).then((res: any) => {
if (res.code == 0) {
getList()
ElMessage.success('删除成功');
done();
}
})
} else {
done();
}
},
}).catch(() => { });
};
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
const getList = () => {
xmfeeList(queryParams.value).then((res: any) => {
if (res.code == 200) {
tableData.value = res.rows
total.value = res.total
tableData.value.forEach((item: any) => {
item.originalData = {}
item.id = item.sfxmdh
item.status = false
})
}
})
}
const getRules = () => {
rptgetrules().then((res: any) => {
reportRuleOptions.value = res.data
})
}
getList()
getRules()
interface DictData {
BT?: Array<any>;
DP?: Array<any>;
LISGROUP1?: Array<any>;
LISGROUP2?: Array<any>;
[key: string]: any[] | undefined; // 添加索引签名以支持动态访问
}
// 字典数据存储
const dictData = ref<DictData>({});
onMounted(async () => {
// 加载病人来源字典
const dictRefs = await comDict('BT', 'DP', 'LISGROUP1', 'LISGROUP2');
// 从 ref 中获取实际数据
dictData.value = {
BT: toRaw(dictRefs.BT.value) || [],
DP: toRaw(dictRefs.DP.value) || [],
LISGROUP1: toRaw(dictRefs.LISGROUP1.value) || [],
LISGROUP2: toRaw(dictRefs.LISGROUP2.value) || [],
};
})
// 字典格式化方法
const formatDict = (v: string, dictType: string) => {
return dictData.value[dictType]?.find((item: any) => item.value == v)?.label;
};
</script>
<style scoped>
/* 保持与之前相同的样式 */
.editable-table-wrapper {
padding: 20px;
max-width: 1800px;
margin: 0 auto;
}
.table-header {
/* display: flex;
justify-content: space-between;
align-items: center; */
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.table-header h3 {
margin: 0;
color: #333;
font-size: 18px;
}
.add-btn {
margin-bottom: 5px;
}
.table-desc {
margin-top: 15px;
padding: 10px 15px;
background-color: #f5f7fa;
border-radius: 4px;
font-size: 14px;
color: #666;
}
/* 单元格样式优化 */
::v-deep .cell-editable {
cursor: pointer;
transition: background-color 0.2s;
}
::v-deep .cell-editable:hover {
background-color: #f0f7ff !important;
}
::v-deep .cell-editing {
background-color: #e6f7ff !important;
}
::v-deep .el-table .el-table__cell {
padding: 6px 0;
}
/* 输入控件样式 */
::v-deep .el-input,
::v-deep .el-input-number,
::v-deep .el-select,
::v-deep .el-color-picker,
::v-deep .el-textarea {
width: 90%;
margin: 0 auto;
}
::v-deep .el-textarea__inner {
min-height: 60px;
resize: vertical;
}
/* 多行文本和颜色显示样式 */
.multi-line-text {
white-space: pre-wrap;
word-break: break-all;
line-height: 1.4;
padding: 0 5px;
}
.color-display {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
}
.color-block {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 3px;
border: 1px solid #ddd;
}
.color-code {
font-size: 12px;
color: #666;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
}
</style>