2025-10-28 17:22:53 +08:00

723 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="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')"
@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')"
@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')"
@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')"
@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')"
@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')"
@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')"
@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')"
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"
@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')"
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}`;
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?.();
}
});
};
// 保存编辑
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: '取消',
type: 'warning',
}
)
.then(() => {
xmfeeDel({ sfxmdh: row.sfxmdh }).then((res: any) => {
if (res.code == 0) {
getList()
ElMessage.success('删除成功');
}
})
})
.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 || v;
};
</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>