收费项目维护模块,优化条码类别模块

This commit is contained in:
wyuu 2025-09-10 14:58:57 +08:00
parent f69122ff6d
commit e61104d7c3
10 changed files with 1442 additions and 834 deletions

View File

@ -18,7 +18,7 @@
"axios": "0.27.2",
"clipboard": "^2.0.11",
"echarts": "5.4.3",
"element-plus": "2.4.3",
"element-plus": "^2.11.2",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"jquery": "^3.7.1",

View File

@ -82,4 +82,5 @@ export function reportFsresult(query?: Object) {
method: 'post',
data: query,
});
}
}

80
src/api/mzcx/index.ts Normal file
View File

@ -0,0 +1,80 @@
//@ts-ignore js语法检查忽略
import request from '@/utils/request';
// 查询条码类别
export function feeitemclassList(query?: Object) {
return request({
url: 'feeitemclass/query',
method: 'get',
params: query,
});
}
// 报告领取规则
export function rptgetrules(query?: Object) {
return request({
url: '/rptgetrule/query',
method: 'get',
params: query,
});
}
// 新增条码类别
export function feeitemclassAdd(query?: Object) {
return request({
url: '/feeitemclass/add',
method: 'post',
data: query,
});
}
// 修改条码类别
export function feeitemclassUpdate(query?: Object) {
return request({
url: '/feeitemclass/update',
method: 'post',
data: query,
});
}
// 删除条码类别
export function feeitemclassDel(query?: Object) {
return request({
url: '/feeitemclass/del',
method: 'delete',
params: query,
});
}
// 收费项目维护列表
export function xmfeeList(query?: Object) {
return request({
url: 'xmfee/query',
method: 'get',
params: query,
});
}
// 收费项目新增
export function xmfeeadd(query?: Object) {
return request({
url: 'xmfee/addXmfee',
method: 'post',
data: query,
});
}
// 收费项目修改
export function xmfeeUpdate(query?: Object) {
return request({
url: 'xmfee/update',
method: 'post',
data: query,
});
}
// 收费项目删除
export function xmfeeDel(query?: Object) {
return request({
url: 'xmfee/del',
method: 'delete',
params: query,
});
}

View File

@ -12,13 +12,14 @@ function decimalToHexColor(decimal: number | string): string {
// 统一转换为数字类型
const num = typeof decimal === 'string' ? parseFloat(decimal) : decimal;
if (isNaN(num)) {
return '#FFFFFF';
}
// 十进制转十六进制并转为大写
let hex = Math.floor(num).toString(16).toUpperCase();
let rgbHex = ''
// 确保十六进制字符串为6位,不足则补0
if (hex.length < 6) {
hex = hex.padStart(6, '0');
@ -28,11 +29,18 @@ function decimalToHexColor(decimal: number | string): string {
const r = hex.substring(4, 6);
const g = hex.substring(2, 4);
const b = hex.substring(0, 2);
const rgbHex = r + g + b;
rgbHex = r + g + b;
return `#${rgbHex}`;
}
// 处理颜色转换为十进制数字
const convertHexToNumber = (hex: string) => {
if (!hex) return
hex = hex.replace('#', '');
const bgr = hex.slice(4, 6) + hex.slice(2, 4) + hex.slice(0, 2);
return parseInt(bgr, 16);
};
/**
* 根据背景色计算对比度文字颜色(黑/白)
* @param bgColor 十六进制背景色(如#FFFF80)
@ -79,5 +87,5 @@ function base64ToBlob(base64: string) {
}
export const classCom = { decimalToHexColor, getContrastTextColor, printPDF }
export const classCom = { decimalToHexColor, convertHexToNumber, getContrastTextColor, printPDF }

View File

@ -1,296 +0,0 @@
<template>
<div class="editable-table-container">
<el-table :data="tableData" border style="width: 100%" @cell-click="handleCellClick"
:cell-class-name="cellClassName">
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="name" label="姓名" width="120">
<template #default="scope">
<template v-if="isEditing(scope.row, scope.column)">
<el-input v-model="scope.row.name" size="small" @blur="handleSave(scope.row, scope.column)"
@keyup.enter="handleSave(scope.row, scope.column)" ref="editInput" auto-focus />
</template>
<template v-else>
<span>{{ scope.row.name }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" width="100" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, scope.column)">
<el-input-number v-model="scope.row.age" :min="0" :max="150" size="small"
@blur="handleSave(scope.row, scope.column)" @keyup.enter="handleSave(scope.row, scope.column)"
ref="editInput" auto-focus />
</template>
<template v-else>
<span>{{ scope.row.age }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱">
<template #default="scope">
<template v-if="isEditing(scope.row, scope.column)">
<el-input v-model="scope.row.email" size="small" @blur="handleSave(scope.row, scope.column)"
@keyup.enter="handleSave(scope.row, scope.column)" ref="editInput" auto-focus />
</template>
<template v-else>
<span>{{ scope.row.email }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="department" label="部门" width="150">
<template #default="scope">
<template v-if="isEditing(scope.row, scope.column)">
<el-select v-model="scope.row.department" size="small" @change="handleSave(scope.row, scope.column)"
@blur="handleSave(scope.row, scope.column)" ref="editSelect" auto-focus>
<el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />
</el-select>
</template>
<template v-else>
<span>{{ getDepartmentLabel(scope.row.department) }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="joinDate" label="入职日期" width="140">
<template #default="scope">
<template v-if="isEditing(scope.row, scope.column)">
<el-date-picker v-model="scope.row.joinDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
size="small" @blur="handleSave(scope.row, scope.column)" @change="handleSave(scope.row, scope.column)"
ref="editDate" auto-focus />
</template>
<template v-else>
<span>{{ scope.row.joinDate }}</span>
</template>
</template>
</el-table-column>
</el-table>
<div class="table-info">
<p>点击任意单元格即可直接编辑该单元格内容</p>
<p>编辑完成后可按Enter键或点击其他区域保存修改</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
// 部门选项
const departments = [
{ label: '技术部', value: 'tech' },
{ label: '市场部', value: 'marketing' },
{ label: '销售部', value: 'sales' },
{ label: '人力资源', value: 'hr' },
{ label: '财务部', value: 'finance' }
];
// 表格数据
const tableData = reactive([
{
id: 1,
name: '张三',
age: 28,
email: 'zhangsan@example.com',
department: 'tech',
joinDate: '2020-03-15',
originalData: {} // 用于保存原始数据
},
{
id: 2,
name: '李四',
age: 32,
email: 'lisi@example.com',
department: 'marketing',
joinDate: '2019-07-22',
originalData: {}
},
{
id: 3,
name: '王五',
age: 45,
email: 'wangwu@example.com',
department: 'sales',
joinDate: '2018-11-05',
originalData: {}
},
{
id: 4,
name: '赵六',
age: 23,
email: 'zhaoliu@example.com',
department: 'hr',
joinDate: '2021-02-18',
originalData: {}
}
]);
// 当前编辑的单元格信息
const editingCell = ref({
row: null,
column: null
});
// 判断单元格是否处于编辑状态
const isEditing = (row, column) => {
return editingCell.value.row === row && editingCell.value.column === column;
};
// 获取部门显示名称
const getDepartmentLabel = (value) => {
const dept = departments.find(item => item.value === value);
return dept ? dept.label : '';
};
// 处理单元格点击事件 - 进入编辑状态
const handleCellClick = (row, column) => {
// ID列不可编辑
if (column.property === 'id') return;
// 如果点击的是当前编辑的单元格,不做处理
if (editingCell.value.row === row && editingCell.value.column === column) {
return;
}
// 先保存之前编辑的单元格(如果有)
if (editingCell.value.row && editingCell.value.column) {
handleSave(editingCell.value.row, editingCell.value.column);
}
// 保存原始值,用于取消编辑时恢复
row.originalData[column.property] = JSON.parse(JSON.stringify(row[column.property]));
// 设置当前编辑的单元格
editingCell.value = { row, column };
// 自动聚焦到输入框
nextTick(() => {
const inputElements = [
document.querySelector('.el-input__inner'),
document.querySelector('.el-input-number__input'),
document.querySelector('.el-select'),
document.querySelector('.el-date-editor input')
];
const activeInput = inputElements.find(el => el);
if (activeInput) {
activeInput.focus();
}
});
};
// 保存编辑
const handleSave = (row, column) => {
// 如果不是编辑状态,直接返回
if (!isEditing(row, column)) return;
const prop = column.property;
let isValid = true;
let errorMessage = '';
// 根据字段类型进行验证
switch (prop) {
case 'name':
if (!row[prop].trim()) {
isValid = false;
errorMessage = '姓名不能为空';
}
break;
case 'email':
if (row[prop] && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(row[prop])) {
isValid = false;
errorMessage = '请输入有效的邮箱地址';
}
break;
case 'age':
if (row[prop] === null || row[prop] <= 0 || row[prop] > 150) {
isValid = false;
errorMessage = '请输入有效的年龄(1-150)';
}
break;
case 'department':
if (!row[prop]) {
isValid = false;
errorMessage = '请选择部门';
}
break;
case 'joinDate':
if (!row[prop]) {
isValid = false;
errorMessage = '请选择入职日期';
}
break;
}
// 验证不通过,恢复原始值并提示
if (!isValid) {
row[prop] = row.originalData[prop];
ElMessage.error(errorMessage);
return;
}
// 清除编辑状态
editingCell.value = { row: null, column: null };
// 实际应用中这里会调用API保存数据
ElMessage.success('数据已更新');
console.log(`保存单元格数据: 行ID=${row.id}, 字段=${prop}, 值=${row[prop]}`);
};
// 单元格样式 - 用于突出显示可编辑单元格
const cellClassName = ({ row, column }) => {
// ID列不可编辑
if (column.property === 'id') return '';
return isEditing(row, column)
? 'cell-editing'
: 'cell-editable';
};
onMounted(() => {
console.log('点击编辑表格组件已加载');
});
</script>
<style scoped>
.editable-table-container {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
}
.table-info {
margin-top: 15px;
color: #666;
font-size: 14px;
line-height: 1.6;
}
::v-deep .cell-editable {
cursor: pointer;
transition: background-color 0.2s;
}
::v-deep .cell-editable:hover {
background-color: #f5f7fa;
}
::v-deep .cell-editing {
background-color: #e6f7ff !important;
}
::v-deep .el-input,
::v-deep .el-input-number,
::v-deep .el-select,
::v-deep .el-date-editor {
width: 100%;
}
/* 移除输入框默认边框,与表格融合更自然 */
::v-deep .cell-editing .el-input__inner,
::v-deep .cell-editing .el-input-number__input,
::v-deep .cell-editing .el-date-editor input {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
</style>

View File

@ -1,529 +0,0 @@
<template>
<div class="app-container">
<el-table :data="tableData" border style="width: 100%" @cell-click="handleCellClick"
:cell-class-name="cellClassName" :row-key="row => row.id" height="calc(100vh - 220px)" highlight-current-row
ref="tableRef">
<!-- 分单类别代号 -->
<el-table-column prop="categoryCode" label="分单类别代号" width="120">
<template #default="scope">
<template v-if="isEditing(scope.row, 'categoryCode')">
<el-input v-model="scope.row.categoryCode" size="small" @blur="handleSave(scope.row, 'categoryCode')"
@keyup.enter="handleSave(scope.row, 'categoryCode')" ref="getInputRef(scope.row.id, 'categoryCode')"
auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'categoryCode' })">
{{ scope.row.categoryCode }}
</span>
</template>
</template>
</el-table-column>
<!-- 类别名称 -->
<el-table-column prop="categoryName" label="类别名称" width="180">
<template #default="scope">
<template v-if="isEditing(scope.row, 'categoryName')">
<el-input v-model="scope.row.categoryName" size="small" @blur="handleSave(scope.row, 'categoryName')"
@keyup.enter="handleSave(scope.row, 'categoryName')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'categoryName' })">
{{ scope.row.categoryName }}
</span>
</template>
</template>
</el-table-column>
<!-- 类别简称 -->
<el-table-column prop="shortName" label="类别简称" width="120">
<template #default="scope">
<template v-if="isEditing(scope.row, 'shortName')">
<el-input v-model="scope.row.shortName" size="small" @blur="handleSave(scope.row, 'shortName')"
@keyup.enter="handleSave(scope.row, 'shortName')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'shortName' })">
{{ scope.row.shortName }}
</span>
</template>
</template>
</el-table-column>
<!-- 条码类别 -->
<el-table-column prop="barcodeType" label="条码类别" width="130">
<template #default="scope">
<template v-if="isEditing(scope.row, 'barcodeType')">
<el-select v-model="scope.row.barcodeType" size="small" @change="handleSave(scope.row, 'barcodeType')"
@blur="handleSave(scope.row, 'barcodeType')" auto-focus>
<el-option v-for="item in barcodeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'barcodeType' })">
{{ getOptionLabel(barcodeTypeOptions, scope.row.barcodeType) }}
</span>
</template>
</template>
</el-table-column>
<!-- 标本类型 -->
<el-table-column prop="specimenType" label="标本类型" width="140">
<template #default="scope">
<template v-if="isEditing(scope.row, 'specimenType')">
<el-select v-model="scope.row.specimenType" size="small" @change="handleSave(scope.row, 'specimenType')"
@blur="handleSave(scope.row, 'specimenType')" auto-focus>
<el-option v-for="item in specimenTypeOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'specimenType' })">
{{ getOptionLabel(specimenTypeOptions, scope.row.specimenType) }}
</span>
</template>
</template>
</el-table-column>
<!-- 标本采集说明 -->
<el-table-column prop="collectionInstructions" label="标本采集说明" width="220">
<template #default="scope">
<template v-if="isEditing(scope.row, 'collectionInstructions')">
<el-input v-model="scope.row.collectionInstructions" size="small" type="textarea" :rows="2"
@blur="handleSave(scope.row, 'collectionInstructions')"
@keyup.enter.ctrl="handleSave(scope.row, 'collectionInstructions')" auto-focus />
</template>
<template v-else>
<div class="multi-line-text"
@click.stop="handleCellClick(scope.row, { property: 'collectionInstructions' })">
{{ scope.row.collectionInstructions || '-' }}
</div>
</template>
</template>
</el-table-column>
<!-- 报告领取规则 -->
<el-table-column prop="reportCollectionRule" label="报告领取规则" width="160">
<template #default="scope">
<template v-if="isEditing(scope.row, 'reportCollectionRule')">
<el-select v-model="scope.row.reportCollectionRule" size="small"
@change="handleSave(scope.row, 'reportCollectionRule')"
@blur="handleSave(scope.row, 'reportCollectionRule')" auto-focus>
<el-option v-for="item in reportRuleOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'reportCollectionRule' })">
{{ getOptionLabel(reportRuleOptions, scope.row.reportCollectionRule) }}
</span>
</template>
</template>
</el-table-column>
<!-- 打印份数 -->
<el-table-column prop="printCopies" label="打印份数" width="100" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'printCopies')">
<el-input-number v-model="scope.row.printCopies" :min="1" :max="10" size="small"
@blur="handleSave(scope.row, 'printCopies')" @keyup.enter="handleSave(scope.row, 'printCopies')"
auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'printCopies' })">
{{ scope.row.printCopies }}
</span>
</template>
</template>
</el-table-column>
<!-- 专业组(样本数) -->
<el-table-column prop="deptSampleCount" label="专业组(样本数)" width="140" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'deptSampleCount')">
<el-input-number v-model="scope.row.deptSampleCount" :min="1" size="small"
@blur="handleSave(scope.row, 'deptSampleCount')" @keyup.enter="handleSave(scope.row, 'deptSampleCount')"
auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'deptSampleCount' })">
{{ scope.row.deptSampleCount }}
</span>
</template>
</template>
</el-table-column>
<!-- 专业组(周转时间) -->
<el-table-column prop="deptTurnaroundTime" label="专业组(周转时间)" width="160" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'deptTurnaroundTime')">
<el-input-number v-model="scope.row.deptTurnaroundTime" :min="0" size="small" suffix="分钟"
@blur="handleSave(scope.row, 'deptTurnaroundTime')"
@keyup.enter="handleSave(scope.row, 'deptTurnaroundTime')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'deptTurnaroundTime' })">
{{ scope.row.deptTurnaroundTime }} 分钟
</span>
</template>
</template>
</el-table-column>
<!-- 抗凝 -->
<el-table-column prop="anticoagulant" label="抗凝" width="100" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.anticoagulant" />
</template>
</el-table-column>
<!-- 血培 -->
<el-table-column prop="bloodCulture" label="血培" width="100" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.bloodCulture" />
</template>
</el-table-column>
<!-- 标识颜色 -->
<el-table-column prop="markerColor" label="标识颜色" width="140" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'markerColor')">
<el-color-picker v-model="scope.row.markerColor" size="small" @change="handleSave(scope.row, 'markerColor')"
@blur="handleSave(scope.row, 'markerColor')" auto-focus />
</template>
<template v-else>
<div class="color-display" @click.stop="handleCellClick(scope.row, { property: 'markerColor' })">
<span class="color-block" :style="{ backgroundColor: scope.row.markerColor }" />
<span class="color-code">{{ scope.row.markerColor }}</span>
</div>
</template>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { ref, reactive, nextTick } from 'vue';
import { ElMessage, } from 'element-plus';
// 选项数据
const barcodeTypeOptions = [
{ label: '一维条码', value: '1d' },
{ label: '二维码', value: '2d' },
{ label: 'RFID', value: 'rfid' },
{ label: '无', value: 'none' }
];
const specimenTypeOptions = [
{ label: '全血', value: 'whole_blood' },
{ label: '血清', value: 'serum' },
{ label: '血浆', value: 'plasma' },
{ label: '尿液', value: 'urine' },
{ label: '脑脊液', value: 'csf' },
{ label: '其他', value: 'other' }
];
const reportRuleOptions = [
{ label: '自助打印', value: 'self_print' },
{ label: '人工窗口', value: 'counter' },
{ label: '科室配送', value: 'dept_delivery' },
{ label: '线上查看', value: 'online' }
];
// 表格数据
const tableData = reactive([
{
id: 1,
categoryCode: 'BH001',
categoryName: '血常规检查',
shortName: '血常规',
barcodeType: '1d',
specimenType: 'whole_blood',
collectionInstructions: '空腹采血,采血量2ml,EDTA抗凝',
reportCollectionRule: 'self_print',
printCopies: 2,
deptSampleCount: 1,
deptTurnaroundTime: 30,
anticoagulant: false,
bloodCulture: false,
markerColor: '#409eff',
originalData: {}
},
{
id: 2,
categoryCode: 'BL002',
categoryName: '生化全项检查',
shortName: '生化全项',
barcodeType: '2d',
specimenType: 'serum',
collectionInstructions: '空腹12小时以上采血,采血量5ml,分离血清',
reportCollectionRule: 'counter',
printCopies: 1,
deptSampleCount: 1,
deptTurnaroundTime: 120,
anticoagulant: false,
bloodCulture: false,
markerColor: '#67c23a',
originalData: {}
}
]);
// 表格引用
const tableRef = ref(null);
// 输入框引用
const inputRefs = ref({});
// 当前编辑状态:{rowId: xxx, field: 'xxx'}
const editingState = ref(null);
// 设置输入框引用
const getInputRef = (rowId, field) => {
return (el) => {
if (el) {
inputRefs.value[`${rowId}-${field}`] = el;
}
};
};
// 判断是否处于编辑状态
const isEditing = (row, field) => {
return editingState.value && editingState.value.rowId === row.id && editingState.value.field === field;
};
// 获取选项标签
const getOptionLabel = (options, value) => {
const option = options.find(item => item.value === value);
return option ? option.label : '';
};
// 处理单元格点击 - 核心修复部分
const handleCellClick = (row, column) => {
// 忽略操作列
if (column.label === '操作') return;
const field = column.property;
// 如果点击的是当前编辑的单元格,不重复处理
if (isEditing(row, field)) {
return;
}
// 保存之前编辑的内容
if (editingState.value) {
const prevRow = tableData.find(r => 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 inputElement = inputRefs.value[inputKey]?.$el?.querySelector('input') ||
inputRefs.value[inputKey]?.$el;
if (inputElement) {
inputElement.focus();
}
});
};
// 保存编辑
const handleSave = (row, field) => {
// 非编辑状态不处理
if (!isEditing(row, field)) return;
let isValid = true;
let errorMessage = '';
// 字段验证
switch (field) {
case 'categoryCode':
if (!row[field]?.trim()) {
isValid = false;
errorMessage = '分单类别代号不能为空';
}
break;
case 'categoryName':
if (!row[field]?.trim()) {
isValid = false;
errorMessage = '类别名称不能为空';
}
break;
case 'shortName':
if (!row[field]?.trim()) {
isValid = false;
errorMessage = '类别简称不能为空';
}
break;
case 'printCopies':
case 'deptSampleCount':
if (row[field] < 1) {
isValid = false;
errorMessage = '数值必须大于0';
}
break;
case 'deptTurnaroundTime':
if (row[field] < 0) {
isValid = false;
errorMessage = '数值不能为负数';
}
break;
}
// 验证失败处理
if (!isValid) {
row[field] = row.originalData[field];
ElMessage.error(errorMessage);
// 保持编辑状态以便修正
return;
}
// 清除编辑状态
editingState.value = null;
ElMessage.success('数据已更新');
};
// 单元格样式
const cellClassName = ({ row, column }) => {
return isEditing(row, column.property)
? 'cell-editing'
: 'cell-editable';
};
// 新增项
const addNewItem = () => {
const newId = Math.max(...tableData.map(item => item.id), 0) + 1;
tableData.unshift({
id: newId,
categoryCode: `NEW${newId}`,
categoryName: '',
shortName: '',
barcodeType: '1d',
specimenType: 'other',
collectionInstructions: '',
reportCollectionRule: 'self_print',
printCopies: 1,
deptSampleCount: 1,
deptTurnaroundTime: 60,
anticoagulant: 'N',
bloodCulture: 'N',
markerColor: '#909399',
originalData: {}
});
ElMessage.info('已添加新类别,请完善信息');
};
</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>

View File

@ -67,8 +67,6 @@ watch(zdlbmc, (val) => {
})
const filterNode = (value, data) => {
console.log(value, data);
if (!value) return true
return data.zdmc.includes(value)
}

View File

@ -0,0 +1,636 @@
<template>
<div class="app-container">
<div class="table-header">
<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>
<el-table :data="tableData" border style="width: 100%" @cell-click="handleCellClick"
:cell-class-name="cellClassName" :row-key="(row: any) => row.id" height="calc(100vh - 260px)"
highlight-current-row ref="tableRef">
<!-- 分单类别代号 -->
<el-table-column prop="sflbdh" label="分单类别代号" width="110">
<template #default="scope">
<template v-if="isEditing(scope.row, 'sflbdh')">
<el-input v-model="scope.row.sflbdh" size="small" @blur="handleSave(scope.row, 'sflbdh')"
@keyup.enter="handleSave(scope.row, 'sflbdh')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'sflbdh' })">
{{ scope.row.sflbdh }}
</span>
</template>
</template>
</el-table-column>
<!-- 类别名称 -->
<el-table-column prop="sflbmc" label="类别名称" width="150">
<template #default="scope">
<template v-if="isEditing(scope.row, 'sflbmc')">
<el-input v-model="scope.row.sflbmc" size="small" @blur="handleSave(scope.row, 'sflbmc')"
@keyup.enter="handleSave(scope.row, 'sflbmc')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'sflbmc' })">
{{ scope.row.sflbmc }}
</span>
</template>
</template>
</el-table-column>
<!-- 类别简称 -->
<el-table-column prop="sflbjc" label="类别简称" width="120">
<template #default="scope">
<template v-if="isEditing(scope.row, 'sflbjc')">
<el-input v-model="scope.row.sflbjc" size="small" @blur="handleSave(scope.row, 'sflbjc')"
@keyup.enter="handleSave(scope.row, 'sflbjc')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'sflbjc' })">
{{ scope.row.sflbjc }}
</span>
</template>
</template>
</el-table-column>
<!-- 条码类别 -->
<el-table-column prop="txmlb" label="条码类别" width="110">
<template #default="scope">
<template v-if="isEditing(scope.row, 'txmlb')">
<el-input v-model="scope.row.txmlb" size="small" @blur="handleSave(scope.row, 'txmlb')"
@keyup.enter="handleSave(scope.row, 'txmlb')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'txmlb' })">
{{ scope.row.txmlb }}
</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="sampletips" label="标本采集说明" width="220">
<template #default="scope">
<template v-if="isEditing(scope.row, 'sampletips')">
<el-input v-model="scope.row.sampletips" size="small" @blur="handleSave(scope.row, 'sampletips')"
@keyup.enter.ctrl="handleSave(scope.row, 'sampletips')" auto-focus />
</template>
<template v-else>
<div class="multi-line-text" @click.stop="handleCellClick(scope.row, { property: 'sampletips' })">
{{ scope.row.sampletips }}
</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="printcnt" label="打印份数" width="120" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'printcnt')">
<el-input-number v-model="scope.row.printcnt" :min="1" :max="9" size="small"
@blur="handleSave(scope.row, 'printcnt')" @keyup.enter="handleSave(scope.row, 'printcnt')" auto-focus />
</template>
<template v-else>
<span @click.stop="handleCellClick(scope.row, { property: 'printcnt' })">
{{ scope.row.printcnt }}
</span>
</template>
</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="kn" label="抗凝" width="100" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.kn" :true-value="'1'" :false-value="'0'" @change="handleSave(scope.row, 'kn')"
:ref="getInputRef(scope.row.id, 'kn')" />
</template>
</el-table-column>
<!-- 血培 -->
<el-table-column prop="xpy" label="血培" width="100" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.xpy" :true-value="'1'" :false-value="'0'"
@change="handleSave(scope.row, 'xpy')" :ref="getInputRef(scope.row.id, 'xpy')" />
</template>
</el-table-column>
<!-- 标识颜色 -->
<el-table-column prop="bkcolor" label="标识颜色" width="140" align="center">
<template #default="scope">
<template v-if="isEditing(scope.row, 'bkcolor')">
<el-color-picker v-model="scope.row.bkcolor" size="small" @change="handleSave(scope.row, 'bkcolor')"
@blur="handleSave(scope.row, 'bkcolor')" auto-focus />
</template>
<template v-else>
<div class="color-display" @click.stop="handleCellClick(scope.row, { property: 'bkcolor' })">
<span class="color-block" :style="{ backgroundColor: scope.row.bkcolor }" />
<span class="color-code">{{ scope.row.bkcolor }}</span>
</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="pageNum" v-model:limit="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 { feeitemclassList, rptgetrules, feeitemclassAdd, feeitemclassUpdate, feeitemclassDel } from '@/api/mzcx/index';
import { classCom } from '@/utils/classCom';
// @ts-ignore
import { comDict } from '@/utils/dict'
const pageNum = ref(1);
const pageSize = ref(10);
const total = ref(0);
const reportRuleOptions = ref<{ bglqdh: string, bglqmc: string, bz: string }[]>([]);
interface tableDataItem {
id: string,
sflbdh: string,
sflbmc: string,
sflbjc: string,
txmlb: string,
yblx: string,
sampletips: string,
bglqdh: number,
printcnt: number,
lisgroup1: string,
lisgroup2: string,
kn: string,
xpy: string,
bkcolor: string,
originalData: object,
flag: boolean,
status: boolean
}
// 表格数据
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 inputElement = inputRefs.value[inputKey]?.$el?.querySelector('input') ||
inputRefs.value[inputKey]?.$el;
if (inputElement) {
inputElement.focus();
}
});
};
// 保存编辑
const handleSave = (row: any, field: string) => {
// 非编辑状态不处理
if (!isEditing(row, field)) return;
let isValid = true;
let errorMessage = '';
// 字段验证
switch (field) {
case 'sflbdh':
if (!row[field]?.trim()) {
isValid = false;
errorMessage = '分单类别代号不能为空';
} else {
// 验证重复(排除当前行自身)
const isDuplicate = tableData.value.some((item: any) => {
return item.sflbdh === row.sflbdh && item.id !== row.id;
});
if (isDuplicate) {
isValid = false;
errorMessage = '分单类别代号已存在';
}
}
break;
// case 'sflbmc':
// if (!row[field]?.trim()) {
// isValid = false;
// errorMessage = '类别名称不能为空';
// }
// break;
// case 'sflbjc':
// if (!row[field]?.trim()) {
// isValid = false;
// errorMessage = '类别简称不能为空';
// }
// break;
case 'printcnt':
if (row[field] < 1) {
isValid = false;
errorMessage = '数值必须大于0';
}
break;
case 'bkcolor':
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, bkcolor, ...restData } = item;
const submitData = { ...restData, bkcolor: classCom.convertHexToNumber(bkcolor) };
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) {
feeitemclassAdd(addList).then((res: any) => {
if (res.code == 0) {
ElMessage.success(res.msg);
getList()
}
})
}
if (updateList.length > 0) {
feeitemclassUpdate(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 addNewItem = () => {
const newId = Array.from({ length: 4 }, () => Math.floor(Math.random() * 10)).join('');
tableData.value.unshift({
id: newId,
sflbdh: `NEW${newId}`,
sflbmc: 'test',
sflbjc: '',
txmlb: '',
yblx: '',
sampletips: '',
bglqdh: 1,
printcnt: 1,
lisgroup1: '',
lisgroup2: '',
kn: '1',
xpy: '0',
bkcolor: '#FFFFFF',
originalData: {},
flag: true,
status: true
});
// ElMessage.info('已添加新类别,请完善信息');
};
// 删除项
const handleDelete = (row: any) => {
ElMessageBox.confirm(`确定删除该${row.sflbdh}吗?`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
beforeClose: (action, instance, done) => {
if (action === "confirm") {
feeitemclassDel({ sflbdh: row.sflbdh }).then((res: any) => {
if (res.code == 0) {
getList()
ElMessage.success('删除成功');
done();
}
})
} else {
done();
}
},
}).catch(() => { });
};
const handleQuery = () => {
pageNum.value = 1
getList()
}
const getList = () => {
feeitemclassList({ pageSize: pageSize.value, pageNum: pageNum.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.sflbdh
item.status = false
item.bkcolor = classCom.decimalToHexColor(item.bkcolor)
})
}
})
}
const getRules = () => {
rptgetrules().then((res: any) => {
reportRuleOptions.value = res.data
})
}
getList()
getRules()
interface DictData {
BT?: Array<any>;
LISGROUP1?: Array<any>;
LISGROUP2?: Array<any>;
[key: string]: any[] | undefined; // 添加索引签名以支持动态访问
}
// 字典数据存储
const dictData = ref<DictData>({});
onMounted(async () => {
// 加载病人来源字典
const dictRefs = await comDict('BT', 'LISGROUP1', 'LISGROUP2');
// 从 ref 中获取实际数据
dictData.value = {
BT: toRaw(dictRefs.BT.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>

View File

@ -0,0 +1,711 @@
<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 />
</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 />
</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 />
</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 />
</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 />
</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 />
</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 />
</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 />
</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 />
</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 />
</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 inputElement = inputRefs.value[inputKey]?.$el?.querySelector('input') ||
inputRefs.value[inputKey]?.$el;
if (inputElement) {
inputElement.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: "取消",
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 () => {
console.log('classCom ==>', classCom.decimalToHexColor("16777215"));
// 加载病人来源字典
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>

View File

@ -32,7 +32,6 @@
// "allowUnknownInTemplate": true // 允许模板中使用未知属性
// }
},
"jsx": "preserve",
"types": ["element-plus/global"],
"include": ["src/**/*","src/**/*.ts", "src/**/*.vue","src/**/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "auto-imports.d.ts"], // **表示任意目录,而 * 表示任意文件。这表明 src 目录中的所有文件都将被编译
"exclude": ["node_modules", "dist"] ,// 指示不需要编译的文件目录