This commit is contained in:
tangw 2025-09-17 10:24:01 +08:00
commit 7e876ce499
26 changed files with 6243 additions and 1147 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",
@ -30,8 +30,10 @@
"pinia": "2.1.7",
"pinyin-pro": "^3.26.0",
"select2": "^4.1.0-rc.0",
"socket.io-client": "^4.8.1",
"vue": "3.4.0",
"vue-cropper": "1.1.1",
"vue-plugin-hiprint": "^0.0.60",
"vue-router": "4.2.5",
"vue3-print-nb": "^0.1.4",
"vue3-select2-component": "^0.1.7",

1487
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
//@ts-ignore js语法检查忽略
import request from '@/utils/request';
// 生成条码
export function addObj(query?: Object) {
return request({
url: 'zyreq/add',
method: 'get',
params: query,
});
}
// 查询申请单列表
export function fetchCodeList(query?: Object) {
return request({
url: '/zyreq/query',
method: 'get',
params: query,
});
}
// 查询项目
export function fetchCodeDetail(query?: Object) {
return request({
url: 'zyreq/detail',
method: 'get',
params: query,
});
}
// 执行条码
export function fetchCodeExec(query?: Object) {
return request({
url: '/zyreq/exec',
method: 'get',
params: query,
});
}
// 获取申请单主表信息
export function fetchCodeReqmain(query?: Object) {
return request({
url: 'zyreq/reqmain',
method: 'get',
params: query,
});
}
// 查询报告单列表
export function reportList(query?: Object) {
return request({
url: '/report/ReportList',
method: 'post',
data: query,
});
}
// 查询报告Pdf
export function reportPrintPdf(query?: Object) {
return request({
url: '/report/pdf',
method: 'get',
params: query,
});
}
// 查询项目报告
export function reportResult(query?: Object) {
return request({
url: '/report/result',
method: 'post',
data: query,
});
}
// 查询微生物报告
export function reportMedList(query?: Object) {
return request({
url: '/report/MedList',
method: 'post',
data: query,
});
}
// 查询微生物报告
export function reportFsresult(query?: Object) {
return request({
url: '/report/fsresult',
method: 'post',
data: query,
});
}

View File

@ -25,6 +25,15 @@ export function updateComDictService(data) {
data
})
}
//删除字典数据
export function delComDictService(data) {
return request({
url: '/comdict/del',
method: 'delete',
data
})
}
// 根据字典类型查询字典数据信息
@ -35,4 +44,3 @@ export function getComDicts(zdlb) {
params: zdlb
})
}

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

@ -0,0 +1,155 @@
//@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,
});
}
// 报告领取规则维护列表
export function rptgetruleList(query?: Object) {
return request({
url: 'rptgetrule/query',
method: 'get',
params: query,
});
}
// 报告领取规则明细列表
export function rptgetruleDetail(query?: Object) {
return request({
url: 'rptgetrule/queryDetail',
method: 'get',
params: query,
});
}
// 报告领取规则新增
export function rptgetruleadd(query?: Object) {
return request({
url: 'rptgetrule/add',
method: 'post',
data: query,
});
}
// 报告领取规则修改
export function rptgetruleUpdate(query?: Object) {
return request({
url: 'rptgetrule/update',
method: 'post',
data: query,
});
}
// 报告领取规则删除
export function rptgetruleDel(query?: Object) {
return request({
url: 'rptgetrule/del',
method: 'delete',
params: query,
});
}
// 报告领取规则明细删除
export function rptgetruleDelDetail(query?: Object) {
return request({
url: 'rptgetrule/delDetail',
method: 'delete',
params: query,
});
}
// 查询条码类别与项目对照
export function feeitemvsList(query?: Object) {
return request({
url: '/feeitemvsclass/query',
method: 'get',
params: query,
});
}
// 保存对照的项目
export function feeitemvAdd(query?: Object) {
return request({
url: '/feeitemvsclass/add',
method: 'post',
data: query,
});
}
// 保存对照的项目
export function feeitemvDel(query?: Object) {
return request({
url: '/feeitemvsclass/del',
method: 'delete',
params: query,
});
}

View File

@ -0,0 +1,44 @@
//@ts-ignore js语法检查忽略
import request from '@/utils/request';
// 查询参数列表
export function paramList(query?: Object) {
return request({
url: '/param/query',
method: 'get',
params: query,
});
}
// 新增参数列表
export function addParam(query?: Object) {
return request({
url: '/param/add',
method: 'post',
data: query,
});
}
// 修改参数列表
export function updateParam(query?: Object) {
return request({
url: '/param/update',
method: 'post',
data: query,
});
}
// 删除参数列表
export function delParam(query?: Object) {
return request({
url: '/param/del',
method: 'delete',
params: query,
});
}
// 查询参数列表树状结构
export function paramTree(query?: Object) {
return request({
url: '/param/queryTree',
method: 'get',
params: query,
});
}

View File

@ -182,3 +182,9 @@ aside {
margin-bottom: 10px;
}
}
.flex-between {
display: flex;
justify-content: space-between;
}

View File

@ -8,14 +8,16 @@
<el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'" />
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px"
v-if="showColumnsType == 'checkbox'">
<el-button circle icon="Menu" />
<template #dropdown>
<el-dropdown-menu>
<template v-for="item in columns" :key="item.key">
<el-dropdown-item>
<el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
<el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)"
:label="item.label" />
</el-dropdown-item>
</template>
</el-dropdown-menu>
@ -24,12 +26,7 @@
</el-tooltip>
</el-row>
<el-dialog :title="title" v-model="open" append-to-body>
<el-transfer
:titles="['显示', '隐藏']"
v-model="value"
:data="columns"
@change="dataChange"
></el-transfer>
<el-transfer :titles="['显示', '隐藏']" v-model="value" :data="columns" @change="dataChange"></el-transfer>
</el-dialog>
</div>
</template>
@ -62,7 +59,7 @@ const props = defineProps({
},
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
const emits = defineEmits(['update:showSearch', 'queryTable', 'updateColumns']);
// 显隐数据
const value = ref([]);
@ -114,6 +111,9 @@ if (props.showColumnsType == 'transfer') {
// 勾选
function checkboxChange(event, label) {
props.columns.filter(item => item.label == label)[0].visible = event;
emits("updateColumns", toRaw(props.columns));
}
</script>
@ -124,9 +124,11 @@ function checkboxChange(event, label) {
display: block;
margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
margin-bottom: 10px;
}
:deep(.el-dropdown-menu__item) {
line-height: 30px;
padding: 0 17px;

View File

@ -0,0 +1,296 @@
<template>
<div class="custom-table-wrapper">
<!-- 表格主体 -->
<el-table ref="tableRef" :data="tableData" v-loading="loading" :stripe="config.stripe || false"
:border="config.border" :fit="config.fit !== true" :show-header="config.showHeader !== false"
:highlight-current-row="config.highlightCurrentRow || false" :row-class-name="config.rowClassName"
:header-cell-style="config.headerCellStyle" :cell-style="config.cellStyle" :max-height="config.maxHeight"
:height="config.height" :size="config.size || 'default'" :empty-text="config.emptyText || '暂无数据'"
@selection-change="handleSelectionChange" @current-change="handleCurrentChange" @row-click="handleRowClick"
@row-dblclick="handleRowDblclick" @sort-change="handleSortChange" v-bind="$attrs">
<!-- 多选列 -->
<el-table-column v-if="config.selection" type="selection" :width="config.selectionWidth || 55"
:fixed="config.selectionFixed" align="center" />
<!-- 序号列 -->
<el-table-column v-if="config.index" type="index" :label="config.indexLabel || '序号'"
:width="config.indexWidth || 60" :fixed="config.indexFixed" align="center" :index="getIndex" />
<!-- 动态列 -->
<template v-for="item in columns" :key="item.prop || item.key">
<!-- 自定义插槽列 行数据插槽(有row属性) -->
<el-table-column v-if="item.slot && item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align || 'left'" :sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip !== false">
<template #default="scope">
<slot :name="item.slot" :row="scope.row" />
</template>
</el-table-column>
<!-- 普通列 -->
<el-table-column v-if="!item.slot && item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align || 'left'" :sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip !== false" :formatter="item.formatter">
<!-- 表头插槽 (无row属性)-->
<template v-if="item.headerSlot" #header="scope">
<slot :name="item.headerSlot" :column="scope.column" :$index="scope.$index" />
</template>
</el-table-column>
</template>
<!-- 操作列 -->
<el-table-column v-if="$slots.action" :label="config.actionLabel || '操作'" :width="config.actionWidth"
:min-width="config.actionMinWidth || 120" :fixed="config.actionFixed || 'right'"
:align="config.actionAlign || 'center'">
<template #default="scope">
<slot name="action" :row="scope.row" :column="scope.column" :$index="scope.$index" />
</template>
</el-table-column>
<!-- 空数据插槽 -->
<template v-if="$slots.empty" #empty>
<slot name="empty" />
</template>
</el-table>
<!-- 分页组件 -->
<div v-if="pagination.show" class="pagination-wrapper">
<el-pagination v-model:current-page="currentPage" v-model:page-size="currentPageSize" :total="pagination.total"
:page-sizes="pagination.pageSizes || [10, 20, 50, 100]"
:layout="pagination.layout || 'total, sizes, prev, pager, next, jumper'"
:background="pagination.background !== false" :small="pagination.small || false"
:disabled="pagination.disabled || false" @size-change="handleSizeChange"
@current-change="handleCurrentPageChange" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch, PropType } from "vue";
import { TableColumnCtx } from 'element-plus';
interface TableColumn {
prop?: string;
key?: string | number;
label?: string;
width?: string | number;
minWidth?: string | number;
fixed?: boolean | string;
align?: string;
sortable?: boolean;
showOverflowTooltip?: boolean;
formatter?: Function;
slot?: string;
headerSlot?: string;
visible?: boolean; // 是否显示列
}
// 在子组件中定义插槽类型
defineSlots<{
// 具名插槽和动态插槽(不含 default)
[slotName: string]: (props: { row?: any; column?: any; $index?: number }) => any;
}>();
// Props 定义
const props = defineProps({
// 表格数据
data: {
type: Array,
default: () => [],
},
// 表格列配置
columns: {
type: Array as PropType<TableColumn[]>,
default: () => [],
},
// 表格配置
config: {
type: Object,
default: () => ({}),
},
// 加载状态
loading: {
type: Boolean,
default: false,
},
// 分页配置
pagination: {
type: Object,
default: () => ({
show: false,
total: 0,
pageSize: 10,
currentPage: 1,
}),
},
});
// Emits 定义
const emit = defineEmits([
"selection-change",
"current-change",
"row-click",
"row-dblclick",
"sort-change",
"page-change",
"size-change",
]);
// 响应式数据
const tableRef = ref();
const currentPage = ref(props.pagination.currentPage || 1); // 第几页
const currentPageSize = ref(props.pagination.pageSize || 10); // 一页显示多少条
// 计算属性
const tableData = computed(() => {
if (props.pagination.show && props.pagination.clientPaging) {
// 客户端分页
const start = (currentPage.value - 1) * currentPageSize.value;
const end = start + currentPageSize.value;
return props.data.slice(start, end);
}
return props.data;
});
// 序号计算
const getIndex = (index: number) => {
if (props.pagination.show && !props.pagination.clientPaging) {
return (currentPage.value - 1) * currentPageSize.value + index + 1;
}
return index + 1;
};
// 事件处理
const handleSelectionChange = (selection: any[]) => {
emit("selection-change", selection);
};
const handleCurrentChange = (currentRow: any, oldCurrentRow: any) => {
emit("current-change", currentRow, oldCurrentRow);
};
const handleRowClick = (row: any, column: TableColumnCtx<any>, event: Event) => {
emit("row-click", row, column, event);
};
const handleRowDblclick = (row: any, column: TableColumnCtx<any>, event: Event) => {
emit("row-dblclick", row, column, event);
};
const handleSortChange = (sortInfo: Object) => {
emit("sort-change", sortInfo);
};
const handleSizeChange = (size: number) => {
currentPageSize.value = size;
currentPage.value = 1;
emit("size-change", {
pageSize: size,
currentPage: 1,
});
};
const handleCurrentPageChange = (page: number) => {
currentPage.value = page;
emit("page-change", {
currentPage: page,
pageSize: currentPageSize.value,
});
};
// 监听分页配置变化
watch(
() => props.pagination.currentPage,
(newVal) => {
if (newVal) {
currentPage.value = newVal;
}
},
);
watch(
() => props.pagination.pageSize,
(newVal) => {
if (newVal) {
currentPageSize.value = newVal;
}
},
);
// 暴露方法
const clearSelection = () => {
tableRef.value?.clearSelection();
};
const toggleRowSelection = (row: Object, selected: boolean) => {
tableRef.value?.toggleRowSelection(row, selected);
};
const toggleAllSelection = () => {
tableRef.value?.toggleAllSelection();
};
const setCurrentRow = (row: Object) => {
tableRef.value?.setCurrentRow(row);
};
const clearSort = () => {
tableRef.value?.clearSort();
};
const doLayout = () => {
tableRef.value?.doLayout();
};
const sort = (prop: string, order: any) => {
tableRef.value?.sort(prop, order);
};
// 导出方法
defineExpose({
clearSelection,
toggleRowSelection,
toggleAllSelection,
setCurrentRow,
clearSort,
doLayout,
sort,
tableRef,
});
</script>
<style scoped lang="scss">
.custom-table-wrapper {
width: 100%;
}
.pagination-wrapper {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 12px 0;
margin-top: 16px;
margin-right: 20px;
}
/* 修改选中行背景色 */
::v-deep .el-table__body tr.current-row>td {
background-color: #add7f7 !important;
}
/* 可选:修改悬停颜色 */
::v-deep .el-table__body tr.hover-row>td {
background-color: #326ca5 !important;
}
/* 响应式设计 */
@media (max-width: 768px) {
.pagination-wrapper {
justify-content: center;
}
.pagination-wrapper :deep(.el-pagination) {
flex-wrap: wrap;
}
}
</style>

16
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
import type { HTMLAttributes } from 'vue'
declare global {
namespace JSX {
interface IntrinsicElements {
[elemName: string]: HTMLAttributes
}
}
}
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -1,11 +1,21 @@
import axios from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth'
import {
ElLoading,
ElMessage
} from 'element-plus'
import {
saveAs
} from 'file-saver'
import {
getToken
} from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { blobValidate } from '@/utils/ruoyi'
import {
blobValidate
} from '@/utils/ruoyi'
const baseURL = import.meta.env.VITE_APP_BASE_API
const baseURL =
import.meta.env.VITE_APP_BASE_API
let downloadLoadingInstance;
export default {
@ -15,7 +25,24 @@ export default {
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
headers: {
'Authorization': 'Bearer ' + getToken()
},
onDownloadProgress: (progressEvent) => {
console.log(progressEvent)
//progressEvent.loaded 下载文件的当前大小
//progressEvent.total 下载文件的总大小 如果后端没有返回 请让他加上
let progressPercent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
let loading = ElLoading.service({
lock: true,
text: `下载中,请稍候${progressPercent}%`,
background: 'rgba(0, 0, 0, 0.7)',
})
loading.setText(`下载中,请稍候${progressPercent}%`)
if (progressPercent == 100) {
loading.close()
}
}
}).then((res) => {
const isBlob = blobValidate(res.data);
if (isBlob) {
@ -32,7 +59,9 @@ export default {
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
headers: {
'Authorization': 'Bearer ' + getToken()
}
}).then((res) => {
const isBlob = blobValidate(res.data);
if (isBlob) {
@ -45,16 +74,23 @@ export default {
},
zip(url, name) {
var url = baseURL + url
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
downloadLoadingInstance = ElLoading.service({
text: "正在下载数据,请稍候",
background: "rgba(0, 0, 0, 0.7)",
})
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
headers: {
'Authorization': 'Bearer ' + getToken()
}
}).then((res) => {
const isBlob = blobValidate(res.data);
if (isBlob) {
const blob = new Blob([res.data], { type: 'application/zip' })
const blob = new Blob([res.data], {
type: 'application/zip'
})
this.saveAs(blob, name)
} else {
this.printErrMsg(res.data);
@ -76,4 +112,3 @@ export default {
ElMessage.error(errMsg);
}
}

View File

@ -0,0 +1,26 @@
// 打印状态管理
import { defineStore } from 'pinia'
export const usePrintStore = defineStore('printStore', {
state: () => ({
isConnected: false, // 存储连接状态
clientInfo: null, // 存储客户端信息
printerList: [] as any[], // 存储打印机列表
defaultPrinter: '' as string, // 存储默认打印机名称
}),
actions: {
setConnected(status: boolean) {
this.isConnected = status
},
setClientInfo(info: any) {
this.clientInfo = info
},
setPrinterList(list: any[]) {
this.printerList = list
this.defaultPrinter = list.find((p) => p.isDefault)?.name || ''
},
setDefaultPrinter(printerName: string) {
this.defaultPrinter = printerName
}
}
})

91
src/utils/classCom.ts Normal file
View File

@ -0,0 +1,91 @@
/**
* 颜色处理工具类
* 提供十进制颜色值转十六进制颜色码(支持BGR转RGB)及文字对比度计算功能
*/
function decimalToHexColor(decimal: number | string): string {
// 处理空值和非数字情况
if (decimal === undefined || decimal === null || decimal === '') {
return '#FFFFFF';
}
// 统一转换为数字类型
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');
}
// BGR转RGB(交换前后两位)
const r = hex.substring(4, 6);
const g = hex.substring(2, 4);
const b = hex.substring(0, 2);
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)
* @returns 对比度文字颜色(#333333或#FFFFFF)
*/
function getContrastTextColor(bgColor: string): string {
// 验证颜色格式
if (!/^#([0-9A-F]{6})$/i.test(bgColor)) {
throw new Error('Invalid hex color format. Expected #RRGGBB');
}
// 提取RGB分量
const r = parseInt(bgColor.slice(1, 3), 16);
const g = parseInt(bgColor.slice(3, 5), 16);
const b = parseInt(bgColor.slice(5, 7), 16);
// 计算亮度(标准 luminance 公式)
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// 根据亮度返回对比色
return luminance > 0.5 ? '#333333' : '#FFFFFF';
}
// 打印base64 pdf
function printPDF(base64: string) {
const blob = base64ToBlob(base64);
const pdfUrl = URL.createObjectURL(blob);
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = pdfUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
setTimeout(() => URL.revokeObjectURL(pdfUrl), 1000); // 释放内存
};
}
function base64ToBlob(base64: string) {
const binaryString = atob(base64.replace(/[\n\r]/g, ''));
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new Blob([bytes], { type: 'application/pdf' });
}
export const classCom = { decimalToHexColor, convertHexToNumber, getContrastTextColor, printPDF }

193
src/utils/hiprintPrinter.ts Normal file
View File

@ -0,0 +1,193 @@
// src/utils/hiprintPrinter.ts
import { io, Socket } from "socket.io-client";
import { ElMessage, ElMessageBox } from 'element-plus'
import { usePrintStore } from '@/store/modules/printStore'
import { onUnmounted } from "vue"
// @ts-ignore
import Download from '@/plugins/download';
// ... 类型定义 ...
type PrinterInfo = {
name: string;
isDefault?: boolean;
[key: string]: any;
};
interface ClientInfo {
id?: string;
version?: string;
os?: string;
[key: string]: string | number | boolean | undefined;
}
type OnPrinterUpdate = (printerList: PrinterInfo[]) => void;
let socket: Socket | null = null;
let clientInfo: ClientInfo | null = null;
// 初始化 Socket 连接
function initSocket(onPrinterUpdate?: OnPrinterUpdate) {
const printStore = usePrintStore();
if (socket) {
socket.disconnect();
}
socket = io("http://localhost:17521", {
transports: ["websocket"],
reconnection: false, //闭自动重连
auth: {
token: "vue-plugin-hiprint",
},
});
// 连接成功
socket.on("connect", () => {
printStore.setConnected(true);
console.log("✅ 已连接 electron-hiprint 客户端");
socket?.emit("getClientInfo");
socket?.emit("refreshPrinterList");
});
// 客户端信息
socket.on("clientInfo", (info: ClientInfo) => {
printStore.setClientInfo(info);
console.log("📱 客户端信息:", info);
});
// 打印机列表 (更新默认打印机)
socket.on("printerList", (list: PrinterInfo[]) => {
printStore.setPrinterList(list);
onPrinterUpdate?.(list);
console.log("🖨️ 打印机列表更新:", list);
});
// 连接断开
socket.on("disconnect", (reason: string) => {
printStore.setConnected(false);
console.error("❌ 客户端连接断开:", reason);
if (reason === "io server disconnect") {
socket?.connect();
}
});
socket.on("connect_error", (err: Error) => {
console.log('连接错误', err);
ElMessageBox.alert(
`连接失败!<br>请确保目标服务器已<a style="color: #1f79db" onclick="downExe()">下载安装</a> 并<a style="color: #1f79db" onclick="yxHiprint()"> 运行 </a> 打印服务!`,
"客户端未连接",
{
dangerouslyUseHTMLString: true,
}
)
printStore.setConnected(false);
socket?.close();
});
}
const downExe = () => {
// 下载逻辑实现
// console.log('开始下载打印服务');
Download.name('hiprint_win_x64-1.0.13.exe', false)
};
const yxHiprint = () => {
// 唤起客户端
window.open("hiprint://");
};
// 挂载到window,使其能被全局访问
(window as any).downExe = downExe;
(window as any).yxHiprint = yxHiprint;
// 自定义打印参数(可选)
interface PrintOptions {
orientation?: "portrait" | "landscape"; // 纵向(landscape=横向)
copies?: number;
monochrome?: boolean; // 是否黑白打印
paperName?: 'A2' | 'A3' | 'A4' | 'A5' | 'A6' | 'letter' | 'legal' | 'tabloid' | 'statement'; //纸张大小
}
/**
* 执行批量静默打印
* @param urls PDF文件URL数组
* @param options 打印选项(可选)
*/
function silentPrint(urls: string[], options?: PrintOptions) {
if (urls.length === 0) return ElMessage.error('没有打印数据');
const printStore = usePrintStore();
const handlePrint = () => {
ElMessage.success('正在打印文件,请稍候...');
urls.forEach(url => silentPrintPdf(url, options));
};
// 已连接直接打印,未连接则初始化后打印
if (printStore.isConnected) {
handlePrint();
} else {
initSocket((printerList) => {
if (printerList.length > 0) {
handlePrint();
} else {
ElMessage.warning('未检测到可用打印机');
}
});
}
}
//静默打印单个PDF文件
function silentPrintPdf(url: string, options?: PrintOptions) {
const printStore = usePrintStore();
const templateId = `pdf-print-${Date.now()}`;
const targetPrinter = printStore.defaultPrinter;
if (!targetPrinter) {
console.log("未检测到打印机,请先连接打印机!");
return;
}
socket?.emit("news", {
client: clientInfo,
printer: targetPrinter,
type: "url_pdf",
templateId: templateId,
pdf_path: url,
});
// console.log(`🚀 PDF打印请求已发送,任务ID:${templateId},打印机:${targetPrinter}`);
}
// 销毁
function destroy() {
const printStore = usePrintStore();
if (socket) {
socket.disconnect();
socket = null;
printStore.setConnected(false);
console.log("🔌 已断开与 electron-hiprint 的连接");
}
}
// 更新 getter 函数以从 store 获取值
export const HiprintPrinter = {
initSocket,
silentPrint,
silentPrintPdf,
destroy,
get isConnected() {
const printStore = usePrintStore();
return printStore.isConnected;
},
get clientInfo() {
const printStore = usePrintStore();
return printStore.clientInfo;
},
get printerList() {
const printStore = usePrintStore();
return printStore.printerList;
},
get defaultPrinter() {
const printStore = usePrintStore();
return printStore.defaultPrinter;
},
};
onUnmounted(() => {
if ('downExe' in window) {
delete (window as any).downExe;
}
});

View File

@ -0,0 +1,624 @@
<template>
<div class="app-container ">
<div ref="compactForm">
<el-row :gutter="10" class="compact-form" v-show="showSearch">
<el-form :model="queryParams" ref="queryRef" :inline="true" :rules="queryRules">
<el-form-item label="日期:" prop="failed1">
<el-date-picker v-model="queryParams.times" type="datetimerange" range-separator="-"
start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss" @change="handletime" />
</el-form-item>
<el-form-item label="医疗机构:" prop="yljg">
<el-select v-model="queryParams.yljg" placeholder="请选择" style="width: 150px;">
<el-option v-for="item in dictData.HOS" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="条码状态:" prop="zt">
<el-select v-model="queryParams.zt" placeholder="请选择" style="width: 100px;">
<el-option v-for="item in dictData.ST" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="" prop="failed4">
<el-select v-model="queryParams.failed4" placeholder="请选择" style="width:140px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="" prop="failed5">
<el-input v-model="queryParams.failed5" clearable placeholder="条件搜索" @keyup.enter="handleQuery"
@clear="handleQuery" />
</el-form-item>
<el-form-item label="">
<el-button icon="search" type="primary" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
</div>
<el-row :gutter="10">
<el-col :span="16" class="right-table">
<div class="title"> 条码生成 </div>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="printHandle">生成条码</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Edit" @click="selectStatusRows">选中所有未打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain @click="printPdf">打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="openBlock('采样登记')">采样登记</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain @click="openBlock('送检登记')">送检登记</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain @click="goReport">报告查询</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain @click="cancelHandle">条码作废</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain>打印机设置</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain>清单打印</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @updateColumns="updateColumns" @queryTable="handleQuery"
:columns="columns"></right-toolbar>
</el-row>
<CustomTable ref="tableRef" :data="tableData" :columns="columns" :config="tableConfig" :loading="loading"
@row-click="rowHandle" @selection-change="selectionChange">
<template #dy="{ row }">
{{ row.zt != 1 ? '✅' : '❌' }}
</template>
<template #jzbz="{ row }">
{{ row.jzbz == '1' ? '急诊' : '非急诊' }}
</template>
<template #jjzt="{ row }">
{{ row.jjzt == '1' ? '计价' : '无' }}
</template>
<template #brly="{ row }">
{{ formatDict(row.brly, 'PT') }}
</template>
<template #zt="{ row }">
{{ formatDict(row.zt.trim(), 'ST') }}
</template>
<template #yljg="{ row }">
{{ formatDict(row.yljg, 'HOS') }}
</template>
</CustomTable>
</el-col>
<el-col :span="8" class="right-table">
<div class="title"> 项目 </div>
<!-- <el-button type="danger" class="mb10">拆分选中项目</el-button> -->
<CustomTable :data="rightTableData" :columns="rightColumns" :config="rightTableConfig">
<template #jjzt="{ row }">
{{ row.jjzt == '1' ? '计价' : '无' }}
</template>
<template #zt="{ row }">
{{ formatDict(row.zt, 'ST') }}
</template>
</CustomTable>
</el-col>
</el-row>
<!-- 采样登记 -->
<el-dialog v-model="dialogVisible" :title="title" draggable :close-on-click-modal="false" width="70vw"
@close="closeDialog">
<div class="flex-between">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-input v-model="sqhText" clearable placeholder="条形码" @keyup.enter="sqdQuery" @clear="sqdQuery" />
</el-col>
<el-col :span="1.5">
<el-button type="primary" @click="sqdQuery" plain>读取</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="delete">删除选中行</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain>打印</el-button>
</el-col>
</el-row>
<!-- <el-button type="primary" plain>保存</el-button> -->
</div>
<CustomTable :data="cyTableData" :columns="cyColumns" :config="cyTableConfig">
<template #zt="{ row }">
{{ formatDict(row.zt, 'ST') }}
</template>
</CustomTable>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw, computed, watch, nextTick, onMounted, } from 'vue';
import { HiprintPrinter } from '@/utils/hiprintPrinter';
import { classCom } from '@/utils/classCom';
import CustomTable from '@/components/tableCom/index.vue'
import { useRouter } from 'vue-router'
import { usePrintStore } from '@/store/modules/printStore'
import { fetchCodeList, addObj, fetchCodeDetail, fetchCodeExec, fetchCodeReqmain } from '@/api/checkCode/index'
import { TableColumnCtx } from 'element-plus';
import dayjs from 'dayjs';
//@ts-ignore
import { comDict } from '@/utils/dict'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
const printStore = usePrintStore();
const compactForm = ref<HTMLElement | null>(null);
const heightForm = ref(90);
const dialogVisible = ref(false)
const title = ref('采样登记')
const sqhText = ref('')
const tableRef = ref()
interface QueryParams {
times: string[]; // 明确类型为字符串元组
yljg: string;
zt: string;
failed4: string;
failed5: string;
}
// 提交表单数据
const queryParams = reactive<QueryParams>({
times: ['2025-07-18 00:00:00', '2025-07-18 23:59:59'],
// times: [],
yljg: '1',
zt: '',
failed4: '',
failed5: '',
});
const showSearch = ref(true);
const queryRef = ref()
// 定义校验规则
const queryRules = ref({
// clientId: [
// { required: true, message: '编号不能为空', trigger: 'blur' },
// ],
// failed3: [
// { required: true, message: '条码状态不能为空', trigger: 'change' },
// ],
})
const options = [
{
value: '1',
label: '通过姓名搜索',
},
{
value: '2',
label: '通过病人号搜索',
},
{
value: '3',
label: '通过床号搜索',
},
]
interface DictData {
SX?: Array<any>;
HOS?: Array<any>;
[key: string]: any[] | undefined; // 添加索引签名以支持动态访问
}
// 字典数据存储
const dictData = ref<DictData>({});
const handleQuery = async () => {
const valid = await queryRef.value.validate().catch(() => { });
if (!valid) return false;
getList()
}
// 重置
const resetQuery = () => {
queryRef.value?.resetFields();
getList()
}
const loading = ref(true);
// 数据源
const tableData = ref([])
// 配置项
const columns = ref([
{ prop: 'zt', label: '打印', visible: true, key: 0, sortable: true, slot: 'dy', },
{ prop: 'ch', label: '床号', visible: true, key: 0, sortable: true, },
{ prop: 'brxm', label: '姓名', visible: true, key: 1, },
{ prop: 'brxbname', label: '性别', align: 'center', visible: true, key: 2, },
{ prop: 'jymd', label: '项目名称', align: 'center', visible: true, key: 3, width: '140px' },
{ prop: 'bgddhname', label: '类别', align: 'center', visible: true, key: 4, width: '120px' },
{ prop: 'sampletips', label: '采样提示', align: 'center', visible: true, key: 5, width: '110px' },
{ prop: 'jzbz', label: '急诊', align: 'center', visible: true, slot: 'jzbz', key: 6, width: '110px' },
{ prop: 'yblx', label: '样本类型', align: 'center', visible: true, key: 7, width: '110px' },
{ prop: 'brdh', label: '病人号', align: 'center', visible: true, key: 8, width: '110px' },
{ prop: 'ksname', label: '科室名称', align: 'center', visible: true, key: 9, width: '110px' },
{ prop: 'sqh', label: '申请号/条码', align: 'center', visible: true, key: 10, width: '110px' },
{ prop: 'zt', label: '状态', align: 'center', visible: true, key: 11, slot: 'zt', width: '110px' },
{ prop: 'brly', label: '病人来源', align: 'center', visible: true, key: 12, slot: 'brly', width: '110px' },
{ prop: 'sqysname', label: '申请医生', align: 'center', visible: true, key: 13, width: '110px' },
{ prop: 'sqsj', label: '申请时间', align: 'center', visible: true, key: 14, width: '170px' },
{ prop: 'jjzt', label: '计价标志', align: 'center', visible: true, key: 15, slot: 'jjzt', },
{ prop: 'zxysname', label: '执行医生', align: 'center', visible: true, key: 16, },
{ prop: 'bbsjrname', label: '送检人', align: 'center', visible: true, key: 17, },
{ prop: 'yljg', label: '医疗机构', align: 'center', visible: true, slot: 'yljg', key: 18, width: '170px' },
])
const single = ref(true);
const multiple = ref(true);
const sqhs = ref([]);
// 单元格样式
const cellStyleHd = ({ row, column, rowIndex, columnIndex }: {
row: any;
column: any;
rowIndex: number;
columnIndex: number;
}) => {
// console.log(row, column, rowIndex, columnIndex);
if (column.label == "急诊" && row.jzbz == "1") {
return { background: '#f00 !important', color: '#fff' };
}
if (column.label == "类别") {
const bgColor = classCom.decimalToHexColor(row.bkcolor);
const textColor = classCom.getContrastTextColor(bgColor);
return {
backgroundColor: `${bgColor} !important`,
color: textColor,
};
}
};
const rowClassNameHd = ({ row, rowIndex }: {
row: any;
rowIndex: number;
}) => {
return 'custom-row-class'; // 返回自定义类名
};
// 点击行
const rowHandle = (row: any, column: TableColumnCtx<any>, event: Event) => {
getRightTable(row)
};
const selectionChange = (selection: any) => {
sqhs.value = selection;
single.value = selection.length != 1;
multiple.value = !selection.length;
}
// 左侧表格配置
const tableConfig = ref({
// stripe: true, // 斑马纹
border: false, // 边框
selection: true, // 多选框
index: false, // 序号
height: '', // 高度
highlightCurrentRow: true, // 高亮当前行
cellStyle: cellStyleHd,
rowClassName: rowClassNameHd,
fit: true
})
// 右侧数据源
const rightTableData = ref([])
// 配置项
const rightColumns = ref([
{ prop: 'sqxmdh', label: '项目代号', visible: true, key: 0, sortable: true, width: '110px' },
{ prop: 'sqxmmc', label: '项目名称', visible: true, key: 1, width: '120px' },
{ prop: 'dj', label: '单价', align: 'center', visible: true, key: 2 },
{ prop: 'sl', label: '数量', align: 'center', visible: true, key: 3 },
{ prop: 'zt', label: '状态', align: 'center', visible: true, slot: 'zt', key: 4 },
{ prop: 'jjzt', label: '计价标志', align: 'center', visible: true, slot: 'jjzt', key: 5 },
])
// 表格配置
const rightTableConfig = ref({
// stripe: true, // 斑马纹
border: false, // 边框
// selection: true, // 多选框
index: false, // 序号
height: '', // 高度
highlightCurrentRow: true, // 高亮当前行
})
// 采样数据源
const cyTableData = ref([])
// 配置项
const cyColumns = ref([
{ prop: 'sqh', label: '申请号/条形码', visible: true, key: 0, sortable: true, width: '140px' },
{ prop: 'ksdh', label: '科室病区', visible: true, key: 1, width: '120px' },
{ prop: 'ch', label: '床号', visible: true, key: 2, sortable: true, },
{ prop: 'brxm', label: '姓名', visible: true, key: 3, },
{ prop: 'brdh', label: '病人号', visible: true, key: 4, },
{ prop: 'brxb', label: '性别', align: 'center', visible: true, key: 5, },
{ prop: 'yblx', label: '样本类型', align: 'center', visible: true, key: 6 },
{ prop: 'zt', label: '状态', align: 'center', visible: true, slot: 'zt', key: 7 },
{ prop: 'jzbz', label: '急诊', align: 'center', visible: true, key: 8 },
{ prop: 'jjzt', label: '采样人', align: 'center', visible: true, key: 8 },
{ prop: 'cysj', label: '采样时间', align: 'center', visible: true, key: 8 },
{ prop: 'zxysname', label: '执行医生', align: 'center', visible: true, key: 8 },
{ prop: 'zxsj', label: '执行时间', align: 'center', visible: true, key: 8 },
])
// 表格配置
const cyTableConfig = ref({
// stripe: true, // 斑马纹
border: false, // 边框
selection: true, // 多选框
index: false, // 序号
height: '100%', // 高度
highlightCurrentRow: true, // 高亮当前行
})
// 采样登记
const openBlock = (value: string) => {
if (sqhs.value.length > 0) {
ElMessageBox.confirm(`确定操作选中的数据吗?`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
beforeClose: async (action, instance, done) => {
if (action === "confirm") {
// 生成请求Promise数组
const requests = sqhs.value.map((item: any) => { return item.sqh });
fetchCodeExec({
sqh: requests.join(','),
userid: 'lis',
status: value == '采样登记' ? 12 : 13,
yljg: 1
}).then((res: any) => {
if (res.code == 0) {
ElMessage.success('调用成功');
done();
getList()
} else {
ElMessage.error('调用失败');
}
})
} else {
done();
}
},
}).catch(() => { });
} else {
title.value = value
dialogVisible.value = true
}
}
// 采样读取
const sqdQuery = () => {
const index = cyTableData.value.findIndex((item: any) => item.sqh == sqhText.value)
if (index !== -1) return ElMessage.warning('该申请单数据已存在!')
fetchCodeReqmain({ sqh: sqhText.value }).then((res: any) => {
if (res.code == 0 && res.data.sqh) {
fetchCodeExec({
sqh: res.data.sqh,
userid: 'lis',
status: 12,
yljg: 1
})
cyTableData.value = cyTableData.value.concat(res.data)
}
})
}
// 关闭弹窗清空
const closeDialog = () => {
cyTableData.value = []
sqhText.value = ''
}
interface ColumnItem {
prop: string;
label: string;
visible: boolean;
key: number;
align: string;
slot: string;
width: string;
}
// 更新列
const updateColumns = (arr: Array<ColumnItem>) => {
columns.value = arr
};
const getRightTable = (row: any) => {
fetchCodeDetail({ sqh: row.sqh }).then((res: any) => {
if (res.code == 0) {
rightTableData.value = res.data
}
})
}
const goReport = () => {
// 跳转到报告查询页面
router.push('/report')
};
const cancelHandle = () => {
if (sqhs.value.length <= 0) return ElMessage.warning('请选择数据!')
ElMessageBox.confirm(`确定作废选中的数据吗?`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
beforeClose: async (action, instance, done) => {
if (action === "confirm") {
const requests = sqhs.value.map((item: any) => { return item.sqh });
fetchCodeExec({
sqh: requests.join(','),
userid: 'lis',
status: 9,
yljg: 1
}).then((res: any) => {
if (res.code == 0) {
ElMessage.success('调用成功');
done();
getList()
} else {
ElMessage.error('调用失败');
}
})
} else {
done();
}
},
}).catch(() => { });
}
onMounted(async () => {
// 初始化3天区间(今天-前两天)
// const end = dayjs().format('YYYY-MM-DD HH:mm:ss');
// const start = dayjs().subtract(2, 'day').format('YYYY-MM-DD HH:mm:ss'); //add 时间往后
// queryParams.times = [start, end];
// console.log(queryParams.times);
getList()
adjustTableHeight();
// 加载病人来源字典
const dictRefs = await comDict('PT', 'HOS', 'ST');
// 从 ref 中获取实际数据
dictData.value = {
PT: toRaw(dictRefs.PT.value) || [],
HOS: toRaw(dictRefs.HOS.value) || [],
ST: toRaw(dictRefs.ST.value) || [],
};
});
// 字典格式化方法
const formatDict = (v: string, dictType: string) => {
return dictData.value[dictType]?.find((item: any) => item.value == v)?.label;
};
// 计算table高度
watch(showSearch, () => {
nextTick(() => {
adjustTableHeight();
})
}, { immediate: true });
function adjustTableHeight() {
if (compactForm.value) {
// 获取高度
heightForm.value = compactForm.value.offsetHeight;
tableConfig.value.height = `calc(72vh - ${heightForm.value}px)`; // 设置表格容器的高度
rightTableConfig.value.height = `calc(76vh - ${heightForm.value}px)`;
}
}
const handletime = (val: Array<string>) => {
// console.log('时间', val);
}
// 生成条码
const printHandle = () => {
const data = {
dept: '0235',
userid: 'lis',
st: '',
et: '',
yljg: queryParams.yljg
}
if (queryParams.times && queryParams.times.length) {
data.st = queryParams.times[0]
data.et = queryParams.times[1]
}
addObj(data).then((res: any) => {
if (res.code == 0) {
getList()
}
})
}
// 查询申请单
const getList = () => {
loading.value = true;
const data = {
dept: '0235',
userid: 'lis',
st: queryParams.times[0],
et: queryParams.times[1],
yljg: queryParams.yljg
}
fetchCodeList(data).then((response: any) => {
if (response.code == 0) {
loading.value = false;
tableData.value = response.data
getRightTable(response.data[0])
tableRef.value.setCurrentRow(response.data[0])
}
})
}
const selectStatusRows = () => {
tableRef.value.clearSelection()
tableData.value.forEach((item: any) => {
if (item.zt == 1) {
tableRef.value.toggleRowSelection(item, true)
}
})
}
// 打印pdf
const printPdf = () => {
if (sqhs.value.length <= 0) return ElMessage.warning('请选择数据!')
ElMessageBox.confirm(`确定打印选中的数据吗?`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
beforeClose: async (action, instance, done) => {
if (action === "confirm") {
const requests = sqhs.value.map((item: any) => { return item.sqh });
fetchCodeExec({
sqh: requests.join(','),
userid: 'lis',
status: 11,
yljg: 1
}).then((res: any) => {
if (res.code == 0) {
done();
getList()
HiprintPrinter.silentPrint(res.data);
}
})
} else {
done();
}
},
}).catch(() => { });
}
</script>
<style scoped lang="scss">
.custom-row-class {
background-color: #c6c3ff !important;
/* 设置行背景色 */
}
.right-table {
border: 1px solid #dcdfe6;
}
.title {
border-bottom: 1px solid #dcdfe6;
margin-bottom: 10px;
padding: 10px;
font-weight: 700;
}
</style>

View File

@ -0,0 +1,700 @@
<template>
<div class="app-container">
<div ref="compactForm">
<el-row :gutter="10" class="compact-form" v-show="showSearch">
<el-form :model="queryParams" ref="queryRef" :inline="true" :rules="queryRules">
<el-form-item label="日期:" prop="times">
<el-date-picker v-model="queryParams.times" type="daterange" range-separator="-" start-placeholder="开始时间"
end-placeholder="结束时间" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="病人状态:" prop="dybz">
<el-select v-model="queryParams.dybz" placeholder="请选择">
<el-option label="已打印" value="1" />
<el-option label="未打印" value="0" />
</el-select>
</el-form-item>
<el-form-item label="科室:" prop="ksdh">
<el-select v-model="queryParams.ksdh" placeholder="请选择" filterable clearable>
<el-option v-for="item in dictData.DP" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="病人类型:" prop="brly">
<el-select v-model="queryParams.brly" placeholder="请选择" style="width: 120px;">
<el-option v-for="item in dictData.PT" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="" prop="">
<el-checkbox v-model="queryParams.alarmflag" label="仅危急值" @change="alarmHandle" size="large" />
<el-checkbox v-model="queryParams.jzbz" label="仅急诊" @change="jzbzHandle" size="large" />
</el-form-item>
<el-form-item label="姓名" prop="brxm">
<el-col :span="1.5">
<el-input v-model="queryParams.brxm" clearable placeholder="姓名" @keyup.enter="handleQuery"
@clear="handleQuery" />
</el-col>
</el-form-item>
<el-form-item label="病人号" prop="brdh">
<el-col :span="1.5">
<el-input v-model="queryParams.brdh" clearable placeholder="病人号" @keyup.enter="handleQuery"
@clear="handleQuery" />
</el-col>
</el-form-item>
<el-form-item label="条形码" prop="sqh">
<el-col :span="1.5">
<el-input v-model="queryParams.sqh" clearable placeholder="条形码" @keyup.enter="handleQuery"
@clear="handleQuery" />
</el-col>
</el-form-item>
<el-form-item label="">
<el-button icon="search" type="primary" @click="handleQuery">
搜索
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
</div>
<el-row :gutter="10">
<el-col :span="14" class="right-table">
<el-row :gutter="10" class="mb8 mt10">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus">打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Edit" @click="selectStatusRows">勾选中所有未打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain :disabled="single" @click="printPdf">打印单张PDF报告</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload">GUID打印报告</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @updateColumns="updateColumns" @queryTable="handleQuery"
:columns="columns"></right-toolbar>
</el-row>
<CustomTable ref="tableRefs" :data="tableData" :columns="columns" :config="tableConfig" :loading="loading"
@selection-change="selectionChange" @row-click="rowHandle">
<template #dybz="{ row }">
{{ row.dybz == 1 ? '✅' : '❌' }}
</template>
<template #alarmflag="{ row }">
{{ row.alarmflag == 1 ? '是' : '否' }}
</template>
<template #jzbz="{ row }">
{{ row.jzbz == 1 ? '急诊' : '危急诊' }}
</template>
</CustomTable>
</el-col>
<el-col :span="10" class="right-table">
<div class="title">检验结果</div>
<div class="mb10">
<el-button type="primary" @click="openDB">对比历史</el-button>
<el-button type="primary" @click="copyData('All')">复制全部数据</el-button>
<el-button type="primary" @click="copyData('N')">复制异常数据</el-button>
<el-button type="primary" @click="copyData('Y')">复制正常数据</el-button>
<el-button type="primary">知识库</el-button>
</div>
<CustomTable :data="rightTableData" :columns="rightColumns" :config="rightTableConfig" :loading="loading"
@row-click="xmrowHandle">
</CustomTable>
<div class="title mt10">抗生素项目</div>
<CustomTable :data="bottomTableData" :columns="bottomColumns" :config="bottomTableConfig" :loading="loading">
</CustomTable>
</el-col>
</el-row>
<!-- 结果对比 -->
<el-dialog v-model="dialogVisible" title="结果对比" draggable :close-on-click-modal="false">
<div class="flex-between">
</div>
<el-table :data="compareTableData" style="width: 100%" @row-click="handleRowClick" highlight-current-row
max-height="350px">
<el-table-column prop="xmdh" label="项目编号" fixed align="center" width="100px" />
<el-table-column prop="xmmc" label="项目名称" fixed align="center" />
<template v-for="(period) in periods" :key="period">
<el-table-column :label="period" :prop="period" :width="150" align="center" />
</template>
</el-table>
<div ref="myEcharts" style="width:100%;height: 350px" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, toRaw, computed, watch, nextTick, onMounted } from 'vue';
import CustomTable from '@/components/tableCom/index.vue'
import { useRouter } from 'vue-router'
import { reportList, reportPrintPdf, reportResult, reportMedList, reportFsresult } from '@/api/checkCode/index'
import dayjs from 'dayjs';
import { ElMessage, ElMessageBox } from 'element-plus'
import { classCom } from '@/utils/classCom'
import * as echarts from 'echarts';
//@ts-ignore
import { comDict } from '@/utils/dict'
const router = useRouter()
const compactForm = ref<HTMLElement | null>(null);
const heightForm = ref(90);
const dialogVisible = ref(false)
const tableRefs = ref()
const myEcharts = ref()
interface QueryParams {
times: string[]; // 明确类型为字符串元组
alarmflag: boolean,
jzbz: boolean,
ksdh: string,
brly: string,
brxm: string,
brdh: string,
sqh: string,
dybz: string,
}
// 提交表单数据
const queryParams = reactive<QueryParams>({
times: ['2025-08-19', '2025-08-19'],
ksdh: '',
brly: '',
alarmflag: false,
jzbz: false,
brxm: '',
brdh: '',
sqh: '',
dybz: '',
});
const single = ref(true);
const multiple = ref(true);
const alarmflag = ref('0')
const jzbz = ref('0')
const showSearch = ref(true);
const queryRef = ref()
// 项目点击数据
const Info = ref<any>({})
// 定义校验规则
const queryRules = ref({
// clientId: [
// { required: true, message: '编号不能为空', trigger: 'blur' },
// ],
// failed3: [
// { required: true, message: '条码状态不能为空', trigger: 'change' },
// ],
})
const handleQuery = async () => {
const valid = await queryRef.value.validate().catch(() => { });
if (!valid) return false;
getList()
}
// 重置
const resetQuery = () => {
queryRef.value?.resetFields();
getList()
}
const loading = false;
// 勾选框数据
const rows = ref<any[]>([])
// 数据源
const tableData = ref([])
// 配置项
const columns = ref<ColumnItem[]>([
{ prop: 'dybz', label: '打印', visible: true, key: 0, slot: 'dybz', },
{ prop: 'jzbz', label: '急诊', visible: true, key: 1, slot: 'jzbz', },
{ prop: 'alarmflag', label: '危急值', align: 'center', visible: true, key: 2, slot: 'alarmflag' },
{ prop: 'jyrq', label: '检验日期', align: 'center', visible: true, key: 3, width: '150px' },
{ prop: 'brdh', label: '病人代号', align: 'center', visible: true, key: 4, width: '110px' },
{ prop: 'ch', label: '床号', align: 'center', visible: true, key: 5, sortable: true, },
{ prop: 'yqdl', label: '仪器', align: 'center', visible: true, key: 6, width: '110px' },
{ prop: 'ybh', label: '样本号', align: 'center', visible: true, key: 7, },
{ prop: 'yblxname', label: '样本类型', align: 'center', visible: true, key: 8, },
{ prop: 'jymd', label: '检验目的', align: 'center', visible: true, key: 9, width: '110px' },
{ prop: 'brlyname', label: '病人类型', align: 'center', visible: true, key: 10, },
{ prop: 'brxbname', label: '性别', align: 'center', visible: true, key: 11, },
{ prop: 'yqmc', label: '仪器名称', align: 'center', visible: true, key: 12, width: '150px' },
{ prop: 'yljg', label: '医疗机构', align: 'center', visible: true, key: 13, width: '150px' },
])
const selectionChange = (selection: any) => {
rows.value = selection
single.value = selection.length != 1;
multiple.value = !selection.length;
}
const rowHandle = (row: any) => {
Info.value = row
const data = {
jyrq: row.jyrq,
yq: row.yq,
ybh: row.ybh,
}
reportResult(data).then((res: any) => {
if (res.code == 0) {
rightTableData.value = res.data
}
})
}
// 打印单张Pdf
const printPdf = () => {
const data = {
jyrq: rows.value[0].jyrq,
yq: rows.value[0].yq,
ybh: rows.value[0].ybh
}
reportPrintPdf(data).then((res: any) => {
if (res.code == 0) {
if (!res.data.base64PDF) return
classCom.printPDF(res.data.base64PDF)
}
})
}
// 单元格样式
const cellStyleHd = ({ row, column, rowIndex, columnIndex }: {
row: any;
column: any;
rowIndex: number;
columnIndex: number;
}) => {
// console.log(row, column, rowIndex, columnIndex);
if (column.label == "急诊" && row.jzbz == "1") {
return { background: '#f00 !important', color: '#fff' };
}
if (column.label == "危急值" && row.alarmflag == "1") {
return { background: '#f00 !important', color: '#fff' };
}
};
// 表格配置
const tableConfig = ref(
{
// stripe: true, // 斑马纹
// border: true, // 边框
selection: true, // 多选框
index: false, // 序号
height: '', // 高度
highlightCurrentRow: true, // 高亮当前行
fit: true,
cellStyle: cellStyleHd
}
)
// 勾选未打印
const selectStatusRows = () => {
tableRefs.value.clearSelection()
tableData.value.forEach((item: any) => {
if (item.dybz != 1) {
tableRefs.value.toggleRowSelection(item, true)
}
})
}
const alarmHandle = (val: boolean) => {
if (val) {
queryParams.jzbz = false
alarmflag.value = '1'
} else {
alarmflag.value = '0'
}
}
const jzbzHandle = (val: boolean) => {
if (val) {
queryParams.alarmflag = false
jzbz.value = '1'
} else {
jzbz.value = '0'
}
}
interface ColumnItem {
prop: string;
label: string;
visible: boolean;
key: number;
align?: string;
slot?: string;
width?: string;
sortable?: boolean;
}
// 更新列
const updateColumns = (arr: Array<ColumnItem>) => {
columns.value = arr
};
// 右侧数据源
const rightTableData = ref([])
// 配置项
const rightColumns = ref([
{ prop: 'xmdh', label: '项目代号', align: 'center', visible: true, key: 0, },
{ prop: 'xmmc', label: '项目名称', align: 'center', visible: true, key: 1 },
{ prop: 'csjg', label: '结果', align: 'center', visible: true, key: 2 },
{ prop: 'refs', label: '参考值', align: 'center', visible: true, key: 3 },
{ prop: 'dw', label: '单位', align: 'center', visible: true, key: 4 },
{ prop: 'jgbz', label: '结果标志', align: 'center', visible: true, key: 5 },
])
// 表格配置
const rightTableConfig = ref(
{
// stripe: true, // 斑马纹
// border: true, // 边框
selection: false, // 多选框
index: false, // 序号
height: '28vh', // 高度
highlightCurrentRow: true, // 高亮当前行
}
)
const xmRow = ref<any>({})
// 查询抗生素
const xmrowHandle = (row: any) => {
xmRow.value = row
const data = {
jyrq: Info.value.jyrq,
yq: Info.value.yq,
ybh: Info.value.ybh,
xmdh: row.xmdh
}
reportMedList(data).then((res: any) => {
if (res.code == 200) {
bottomTableData.value = res.rows
}
})
}
const periods = ref<string[]>([])
const compareTableData = ref([])
const aaaa = () => {
}
// 历史数据对比
const openDB = () => {
// 00507620
compareTableData.value = []
if (!xmRow.value.xmdh) return ElMessage.error('至少选择一条数据进行对比')
const data = {
brdh: Info.value.brdh,
xmdh: xmRow.value.xmdh
}
reportFsresult(data).then((res: any) => {
periods.value = []
if (res.code == 0) {
const arr: string[] = res.data.map((item: any) => item.confirmdt);
periods.value = [...new Set(arr)];
res.data.forEach((v: any) => {
arr.forEach((item: any) => {
if (item == v.confirmdt) {
v[item] = v.csjg
}
})
});
// @ts-ignore 忽略重组数据动态时间key
compareTableData.value = formatGroupedData(res.data)
dialogVisible.value = true
nextTick(() => {
handleRowClick(compareTableData.value[0])
})
}
})
}
interface copyItem {
xmdh: string;
xmmc: string;
csjg: string | number;
refs: string | number;
dw: string;
jgbz: string;
}
// 复制函数
const copyData = (type: string) => {
let str = ''
const getCommonRow = (item: copyItem) => `${item.xmdh}\t${item.xmmc}\t${item.csjg}\t${item.refs}\t${item.dw}\t${item.jgbz}\t\r\n`;
rightTableData.value.forEach((v: any) => {
if (type == 'All') {
str = str + getCommonRow(v)
str = str + v.xmmc + v.csjg + v.dw + + ',';
}
if (type == 'N' && v.jgbz != 'M' && v.jgbz != '') {
str = str + getCommonRow(v)
}
if (type == 'Y' && v.jgbz == 'M' && v.jgbz != '') {
str = str + getCommonRow(v)
}
})
var input = document.createElement('textarea');
if (!str) return ElMessage.error('没有数据可复制');
// 把文字放进input中,供复制
input.value = str;
document.body.appendChild(input);
// 选中创建的input
input.select();
var copy_result = document.execCommand('copy');
if (copy_result) {
ElMessage.success('复制成功');
} else {
ElMessage.error('复制失败');
}
// 移除
document.body.removeChild(input);
}
// 数据重组
const formatGroupedData = (data: any[]) => {
if (!data || data.length === 0) return [];
const groupMap: any = {};
data.forEach(item => {
const key = item.xmdh;
if (!groupMap[key]) {
groupMap[key] = {
xmdh: item.xmdh,
xmmc: item.xmmc,
// confirmdt: item.confirmdt
};
}
const dateKeys = Object.keys(item).filter(key =>
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(key) // 匹配 YYYY-MM-DD HH:mm:ss 时间格式
);
// 将日期键值对添加到分组对象
dateKeys.forEach(dateKey => {
groupMap[key][dateKey] = item[dateKey];
});
});
// 将 Map 转为数组
return Object.values(groupMap);
};
const handleRowClick = (row: any) => {
getEcharts(row)
}
const getEcharts = (data: any) => {
const dateKeys = Object.keys(data).filter(key =>
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(key)
);
const Intance = echarts.init(myEcharts.value);
const xData = dateKeys;
let seriesData = []
seriesData = dateKeys.map(item => data[item]);
const option = {
title: {
text: data.xmmc,
top: '5',
right: '5%',
left: '20',
// bottom: '20%'
// subtext: 'Feature Sample: Gradient Color, Shadow, Click Zoom'
},
tooltip: {
trigger: 'axis'
},
grid: {
top: '20%',
left: '3%',
right: '4%',
// bottom: '3%',
height: '80%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisTick: {
show: false
},
axisLine: {
lineStyle: {
// color: 'rgba(193, 207, 220, 1)',
}
},
axisLabel: {
rotate: 30,
textStyle: {
color: "#8D949B "
}
},
},
yAxis: [{
// name: '单位(mm)',
type: 'value',
axisLabel: {
textStyle: {
color: "#8D949B"
},
// formatter: '{value}%'
},
axisLine: {
show: false,
lineStyle: {
color: '#24abf2'
}
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
},
},
}
],
series: [{
name: data.xmmc,
type: 'line',
data: seriesData,
symbol: 'circle',
symbolSize: 5, //标记的大小
lineStyle: {
normal: {
width: 2,
color: '#24abf2',
}
},
itemStyle: {
normal: {
color: '#24abf2',
}
},
smooth: true
}
]
};
Intance.setOption(option);
}
// 右侧数据源
const bottomTableData = ref([])
// 配置项
const bottomColumns = ref([
{ prop: 'ywdh', label: '抗生素编号', align: 'center', visible: true, key: 0 },
{ prop: 'ywmc', label: '抗生素名称', align: 'center', visible: true, key: 1 },
{ prop: 'csjg', label: '测试结果', align: 'center', visible: true, key: 2 },
{ prop: 'jgbz', label: '结果标志', align: 'center', visible: true, key: 3 },
])
// 表格配置
const bottomTableConfig = ref(
{
// stripe: true, // 斑马纹
// border: true, // 边框
selection: false, // 多选框
index: false, // 序号
height: '28vh', // 高度
highlightCurrentRow: true, // 高亮当前行
}
)
interface DictData {
DP?: Array<any>;
PT?: Array<any>;
[key: string]: any[] | undefined; // 添加索引签名以支持动态访问
}
// 字典数据存储
const dictData = ref<DictData>({});
onMounted(async () => {
// 初始化3天区间(今天-前两天)
// const end = dayjs().format('YYYY-MM-DD HH:mm:ss');
// const start = dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'); //add 时间往后
// queryParams.times = [start, end];
// console.log(queryParams.times);
getList()
adjustTableHeight();
// 加载病人来源字典
const dictRefs = await comDict('PT', 'DP');
// 从 ref 中获取实际数据
dictData.value = {
PT: toRaw(dictRefs.PT.value) || [],
DP: toRaw(dictRefs.DP.value) || [],
};
});
const getList = () => {
const data = {
alarmflag: alarmflag.value,
jzbz: jzbz.value,
dybz: queryParams.dybz,
ksdh: queryParams.ksdh,
brly: queryParams.brly,
brxm: queryParams.brxm,
brdh: queryParams.brdh,
sqh: queryParams.sqh,
st: '',
et: '',
yljg: 1
}
if (queryParams.times && queryParams.times.length) {
data.st = queryParams.times[0]
data.et = queryParams.times[1]
}
reportList(data).then((res: any) => {
if (res.code == 0) {
tableData.value = res.data
tableRefs.value.setCurrentRow(res.data[0])
rowHandle(res.data[0])
}
})
}
// 计算table高度
watch(showSearch, () => {
nextTick(() => {
adjustTableHeight();
})
}, { immediate: true });
function adjustTableHeight() {
if (compactForm.value) {
// 获取高度
heightForm.value = compactForm.value.offsetHeight;
tableConfig.value.height = `calc(80vh - ${heightForm.value}px)`;
rightTableConfig.value.height = `calc(38vh - ${heightForm.value}px)`;
bottomTableConfig.value.height = `calc(35vh - ${heightForm.value} }px)`;
}
}
</script>
<style scoped lang="scss">
.right-table {
border: 1px solid #dcdfe6;
}
.title {
border-bottom: 1px solid #dcdfe6;
margin-bottom: 10px;
padding: 10px;
font-weight: 700;
}
</style>

View File

@ -1,7 +1,8 @@
<script setup>
import { computed } from "vue";
import { computed, reactive, ref, onMounted, nextTick } from "vue";
import { getToken } from "@/utils/auth";
import {queryComDictListService,addComDictService,updateComDictService} from "../../../api/liswork/dict/ComDict.js";
import { queryComDictListService, addComDictService, updateComDictService, delComDictService } from "../../../api/liswork/dict/ComDict.js";
const { proxy } = getCurrentInstance();
@ -20,6 +21,7 @@ const data = reactive({
dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
},
});
const { queryParams, form, rules } = toRefs(data);
const single = ref(true);
const multiple = ref(true);
@ -50,8 +52,32 @@ const upload = reactive({
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + "/comdict/importData"
});
const treeRef = ref(null)
const defaultProps = {
children: 'children',
label: 'zdmc',
id: 'zddh'
}
function resetData(zdlb){
const info = ref({})
watch(zdlbmc, (val) => {
console.log(ref(treeRef.value));
treeRef.value?.filter(val)
})
const filterNode = (value, data) => {
if (!value) return true
return data.zdmc.includes(value)
}
const nodeHandle = (data) => {
queryParams.value.pageNum = 1
queryParams.value.zdlb = data.zddh;
getList()
// resetData(data.zdlb)
}
function resetData(zdlb) {
queryParams.value = {
pageNum: 1,
pageSize: 15,
@ -61,8 +87,7 @@ function resetData(zdlb){
yljg: '1'
}
}
function getTypeList(){
function getTypeList() {
loading.value = true;
queryParams.value.pageSize = 1000;
queryParams.value.zdlb = '00'
@ -71,14 +96,11 @@ function getTypeList(){
total.value = response.total;
loading.value = false;
resetData()
queryParams.value.zdlb = response.rows[1].zddh
getList()
});
}
function refreshDetail(zdlb){
queryParams.value.zdlb = zdlb;
getList()
resetData(zdlb)
}
/** 查询字典明细列表 */
@ -93,7 +115,7 @@ function getList() {
}
//查询
function handleQuery(){
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
@ -109,18 +131,16 @@ function reset() {
proxy.resetForm("dictRef");
}
//重置
function resetQuery(){
queryParams.value = {
zdmc: '',
zddh: ''
}
function resetQuery() {
queryParams.value.zdmc = ''
queryParams.value.zddh = ''
proxy.resetForm("queryRef");
handleQuery();
}
//新增
function handleAdd(){
if(queryParams.value.zdlb === undefined || queryParams.value.zdlb === ''){
function handleAdd() {
if (queryParams.value.zdlb === undefined || queryParams.value.zdlb === '') {
proxy.$modal.msgWarning("请先选择字典大类");
return;
}
@ -130,13 +150,20 @@ function handleAdd(){
}
//修改
function handleUpdate(){
function handleUpdate(row) {
reset()
form.value = row.zdlb ? row : info.value
open.value = true;
}
//删除
function handleDelete(){
function handleDelete(row) {
proxy.$modal.confirm('是否确认删除字典名称为"' + row.zdmc + '"的数据项?').then(function () {
return delComDictService(row);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => { });
}
/** 导入按钮操作 */
function handleImport() {
@ -147,7 +174,7 @@ function handleImport() {
function handleExport() {
proxy.download("comdict/export", {
...queryParams.value,
},`user_${new Date().getTime()}.xlsx`);
}, `user_${new Date().getTime()}.xlsx`);
};
/** 下载模板操作 */
function importTemplate() {
@ -173,12 +200,13 @@ const handleFileSuccess = (response, file, fileList) => {
//多选框选中数据
function handleSelectionChange(){
function handleSelectionChange(selection) {
info.value = selection[0]
single.value = selection.length != 1;
}
//查询字典大类
function handleQueryDetail(){
function handleQueryDetail() {
queryParams.value.zdmc = zdlbmc.value
getTypeList()
resetData()
@ -189,64 +217,66 @@ function submitForm() {
if (valid) {
if (form.value.zdlb != undefined && form.value.zdlb != '') {
updateComDictService(form.value).then(response => {
if (response.code == 0) {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
}).catch(error => {
reset();
});
}
})
} else {
form.value.zdlb = queryParams.value.zdlb;
form.value.yljg = 1;
addComDictService(form.value).then(response => {
if (response.code == 0) {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
}).catch(error => {
form.value.zdlb = '';
});
}
})
}
}
});
}
// 取消操作(保留这一份,删除后面重复的)
function cancel() {
open.value = false;
reset();
}
onMounted(() => {
getTypeList()
})
getTypeList()
</script>
<template>
<div class="app-container">
<div class="app-container">
<el-row :gutter="20">
<el-col :span="4" :xs="24">
<div class="head-container">
<el-input v-model="zdlbmc" clearable placeholder="请输入字典名称" prefix-icon="Search" style="margin-bottom: 20px" @keyup.enter="handleQueryDetail" @clear="handleQueryDetail" />
<el-input v-model="zdlbmc" clearable placeholder="请输入字典名称" prefix-icon="Search" style="margin-bottom: 20px" />
</div>
<div class="head-container">
<div class="card-content">
<p class="clickable" v-for="item in typeList" :key="item.zdlb" @click="refreshDetail(item.zddh)">{{ item.zdmc }}</p>
</div>
<div class="card-content">
<p class="clickable" v-for="item in typeList" :key="item.zdlb" @click="refreshDetail(item.zddh)">{{ item.zdmc }}</p>
<el-tree ref="treeRef" class="filter-tree" :data="typeList" :props="defaultProps" default-expand-all
:filter-node-method="filterNode" @node-click="nodeHandle" highlight-current />
</div>
</div>
</el-col>
<!-- 字典明细-->
<!-- 字典明细-->
<el-col :span="20" :xs="24">
<!-- 表单区域-->
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item class="cus-el-form-item" label="名称" prop="dictName">
<el-input v-model="queryParams.zdmc" placeholder="请输入名称" clearable style="width: 240px" @keyup.enter="handleQuery"/>
<el-input v-model="queryParams.zdmc" placeholder="请输入名称" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item class="cus-el-form-item" label="明细代号" prop="status">
<el-input v-model="queryParams.zddh" placeholder="请输入明细代号" clearable style="width: 240px" @keyup.enter="handleQuery"/>
<el-input v-model="queryParams.zddh" placeholder="请输入明细代号" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@ -256,37 +286,45 @@ getTypeList()
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd"
v-hasPermi="['system:dict:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['system:dict:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate"
v-hasPermi="['system:dict:edit']">修改</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
v-hasPermi="['system:dict:remove']">删除</el-button>
</el-col> -->
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport"
v-hasPermi="['system:dict:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:dict:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['system:dict:import']">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
<el-button type="warning" plain icon="Download" @click="handleExport"
v-hasPermi="['system:dict:export']">导出</el-button>
</el-col>
</el-row>
<!-- 表格区域-->
<el-table v-loading="loading" :data="typeDetail" @selection-change="handleSelectionChange">
<el-table v-loading="loading" :data="typeDetail" @selection-change="handleSelectionChange" height="65vh">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label='字典类型' align="center" prop="zdlb" :show-overflow-tooltip="true"/>
<el-table-column label='字典类型' align="center" prop="zdlb" :show-overflow-tooltip="true" />
<el-table-column label="明细代号" align="center" prop="zddh" />
<el-table-column label="名称" align="center" prop="zdmc" :show-overflow-tooltip="true"/>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="名称" align="center" prop="zdmc" :show-overflow-tooltip="true" />
<el-table-column label="备注" align="center" prop="bz" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="{row}">
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['system:dict:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(row)" v-hasPermi="['system:dict:remove']">删除</el-button>
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)"
v-hasPermi="['system:dict:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(row)"
v-hasPermi="['system:dict:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList"/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-col>
</el-row>
@ -316,18 +354,9 @@ getTypeList()
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading"
:on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<template #tip>
@ -336,7 +365,8 @@ getTypeList()
<el-checkbox v-model="upload.updateSupport" />是否覆盖已经存在的字典数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;"
@click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@ -347,24 +377,32 @@ getTypeList()
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<style scoped lang="scss">
.card-content {
.card-content { /* 错误:嵌套了同名类,导致样式不生效 */
.card-content {
max-height: 450px;
/* 设置最大高度 */
overflow-y: auto;
/* 超出高度时显示垂直滚动条 */
padding: 16px;
/* 保持与卡片默认一致的内边距 */
}
.clickable:hover {
cursor: pointer; /* 鼠标悬停时显示手型 */
transition: all 0.3s; /* 平滑过渡效果 */
.clickable:hover {
cursor: pointer;
/* 鼠标悬停时显示手型 */
transition: all 0.3s;
/* 平滑过渡效果 */
color: blue;
}}
}
}
.filter-tree {
max-height: 70vh;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,646 @@
<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 :ref="getInputRef(scope.row.id, 'sflbdh')" />
</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 :ref="getInputRef(scope.row.id, 'sflbmc')" />
</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 :ref="getInputRef(scope.row.id, 'sflbjc')" />
</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 :ref="getInputRef(scope.row.id, 'txmlb')" />
</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
:ref="getInputRef(scope.row.id, 'sampletips')" />
</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
:ref="getInputRef(scope.row.id, 'printcnt')" />
</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')" />
</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')" />
</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 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 '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: '0',
xpy: '0',
bkcolor: '#FFFFFF',
originalData: {},
flag: true,
status: true
});
// ElMessage.info('已添加新类别,请完善信息');
};
// 删除项
const handleDelete = (row: any) => {
ElMessageBox.confirm(
`确定删除该${row.sflbdh}吗?`, "提示",
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
feeitemclassDel({ sflbdh: row.sflbdh }).then((res: any) => {
if (res.code == 0) {
getList()
ElMessage.success('删除成功');
}
})
})
.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,168 @@
<template>
<div class="app-container">
<!-- 左侧表格 -->
<div style="flex:1">
<el-input v-model="leftSearchValue" placeholder="请输入类别名称或代号" />
<el-table :data="filteredLeftTableData" @row-click="handleLeftRowClick" highlight-current-row
:cell-style="cellStyle" height="80vh">
<el-table-column prop="sflbdh" label="分单类别代号" width="110" />
<el-table-column prop="sflbmc" label="类别名称" />
</el-table>
</div>
<!-- 中间表格 -->
<div style="flex:2">
<el-input v-model="middleSearchValue" placeholder="请输入项目名称或代号" />
<el-table :data="filteredMiddleTableData" @row-dblclick="handleMiddleRowDblClick" height="80vh">
<el-table-column prop="sfxmdh" label="项目代号" />
<el-table-column prop="sfxmmc" label="当前类别包含门诊项目" />
</el-table>
</div>
<!-- 右侧表格 -->
<div style="flex:3">
<el-input v-model="rightSearchValue" placeholder="请输入项目名称、代号或助记符" />
<el-table :data="filteredRightTableData" @row-dblclick="handleRightRowDblClick" height="80vh">
<el-table-column prop="sfxmdh" label="门诊项目代号" />
<el-table-column prop="sfxmmc" label="门诊项目名称" />
<el-table-column prop="zjf" label="助记符" />
</el-table>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { feeitemclassList, feeitemvsList, feeitemvAdd, feeitemvDel } from '@/api/mzcx/index';
import { classCom } from '@/utils/classCom';
interface FeeItem {
sfxmdh: string;
sfxmmc: string;
zjf?: string;
xh?: number;
sflbdh?: string;
}
const leftSearchValue = ref('');
const middleSearchValue = ref('');
const rightSearchValue = ref('');
const leftTableData = ref<Array<{ sflbdh: string; sflbmc: string }>>([]);
const middleTableData = ref<FeeItem[]>([]);
const rightTableData = ref<FeeItem[]>([]);
const sflbdhData = ref<{ sflbdh?: string; sflbmc?: string }>({});
// 过滤后的数据
const filteredLeftTableData = computed(() => {
const searchVal = leftSearchValue.value.toLowerCase().trim();
if (!searchVal) return leftTableData.value;
return leftTableData.value.filter(item =>
item.sflbdh.toLowerCase().includes(searchVal) ||
item.sflbmc.toLowerCase().includes(searchVal)
);
});
const filteredMiddleTableData = computed(() => {
const searchVal = middleSearchValue.value.toLowerCase().trim();
if (!searchVal) return middleTableData.value;
return middleTableData.value.filter(item =>
item.sfxmdh.toLowerCase().includes(searchVal) ||
item.sfxmmc.toLowerCase().includes(searchVal)
);
});
const filteredRightTableData = computed(() => {
const searchVal = rightSearchValue.value.toLowerCase().trim();
if (!searchVal) return rightTableData.value;
return rightTableData.value.filter(item =>
item.sfxmdh.toLowerCase().includes(searchVal) ||
item.sfxmmc.toLowerCase().includes(searchVal) ||
(item.zjf && item.zjf.toLowerCase().includes(searchVal))
);
});
// 左侧表格行点击事件
const handleLeftRowClick = (row: any) => {
sflbdhData.value = row;
feeitemvsList({ sflbdh: sflbdhData.value.sflbdh }).then((res: any) => {
middleTableData.value = res.data.labFeeitemVsClassList;
rightTableData.value = res.data.xmFeeList;
}).catch((err: any) => {
console.error('请求数据失败:', err);
});
};
// 中间表格行双击事件
const handleMiddleRowDblClick = (row: any) => {
const index = middleTableData.value.findIndex(
(item: any) => item.sfxmdh === row.sfxmdh,
);
if (index !== -1) {
const removedItem = middleTableData.value[index];
feeitemvDel({ sfxmdh: removedItem.sfxmdh }).then((res: any) => {
if (res.code == 0) {
middleTableData.value.splice(index, 1)
rightTableData.value.push(removedItem);
}
})
}
};
// 右侧表格行双击事件
const handleRightRowDblClick = (row: any) => {
const index = rightTableData.value.findIndex(
(item: any) => item.sfxmdh === row.sfxmdh,
);
if (index !== -1) {
const removedItem = rightTableData.value[index];
removedItem.xh = middleTableData.value.length + 1;
removedItem.sflbdh = sflbdhData.value.sflbdh;
feeitemvAdd([removedItem]).then((res: any) => {
if (res.code == 0) {
rightTableData.value.splice(index, 1)
middleTableData.value.push(removedItem);
}
})
}
};
const getList = () => {
feeitemclassList().then((res: any) => {
if (res.code == 200) {
leftTableData.value = res.rows;
handleLeftRowClick(res.rows[0]);
}
})
}
getList()
// 单元格样式
const cellStyle = ({ row, column, rowIndex, columnIndex }: {
row: any;
column: any;
rowIndex: number;
columnIndex: number;
}) => {
const bgColor = classCom.decimalToHexColor(row.bkcolor);
const textColor = classCom.getContrastTextColor(bgColor);
return {
backgroundColor: `${bgColor} !important`,
color: textColor,
};
}
</script>
<style scoped>
.app-container {
display: flex;
padding: 20px;
gap: 35px;
}
</style>

View File

@ -0,0 +1,722 @@
<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;
};
</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

@ -1,37 +1,34 @@
<template>
<div class="app-container">
<el-row :gutter="5" class="compact-form" :span="24" >
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-position="left" >
<el-row :gutter="5" class="compact-form" :span="24">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-position="left">
<el-col :span="24" :xs="24">
<el-form-item label="" label-width="0px" prop="brdh" >
<el-form-item label="" label-width="0px" prop="brdh">
<el-col :span="6">
<div> 就诊卡号/病历号/磁卡号: </div>
<el-input
v-model="queryParams.brdh"
placeholder="就诊卡号/病历号/磁卡号"
clearable
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.brdh" placeholder="就诊卡号/病历号/磁卡号" clearable @keyup.enter="handleQuery" />
</el-col>
<el-col :span="2">
<el-radio-group v-model="queryParams.zt" size="default">
<el-radio v-for="(item, index) in showconfirmOptions" :key="index" :label="item.value"
:disabled="item.disabled">{{item.label}}</el-radio>
:disabled="item.disabled">{{ item.label }}</el-radio>
</el-radio-group>
</el-col>
<el-col :span="1"> <div> 申请获取期限: </div></el-col>
<el-col :span="1">
<div> 申请获取期限: </div>
</el-col>
<el-col :span="6">
<el-radio-group v-model="queryParams.subday" size="default">
<el-radio v-for="(item, index) in subdayOptions" :key="index" :label="item.value"
:disabled="item.disabled">{{item.label}}</el-radio>
:disabled="item.disabled">{{ item.label }}</el-radio>
</el-radio-group>
</el-col>
<el-col :span="2">
<el-select v-model="queryParams.cardtype" placeholder="卡类型" clearable :style="{width: '100%'}">
<el-option v-for="(item, index) in cardtypeOptions" :key="index" :label="item.label"
:value="item.value" :disabled="item.disabled"></el-option>
<el-select v-model="queryParams.cardtype" placeholder="卡类型" clearable :style="{ width: '100%' }">
<el-option v-for="(item, index) in cardtypeOptions" :key="index" :label="item.label" :value="item.value"
:disabled="item.disabled"></el-option>
</el-select>
</el-col>
<el-col :span="1.5">
@ -47,112 +44,78 @@
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Search"
@click="handleQuery"
v-hasPermi="['system:reqmain:Search']"
>查询</el-button>
<el-button type="primary" plain icon="Search" @click="handleQuery"
v-hasPermi="['system:reqmain:Search']">查询</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Printer"
:disabled="single"
@click="printAll"
v-hasPermi="['system:reqmain:add']"
>打印</el-button>
<el-button type="success" plain icon="Printer" :disabled="single" @click="printAll"
v-hasPermi="['system:reqmain:add']">打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Printer"
:disabled="freesingle"
@click="printSigne"
v-hasPermi="['system:reqmain:edit']"
>单打条码</el-button>
<el-button type="success" plain icon="Printer" :disabled="freesingle" @click="printSigne"
v-hasPermi="['system:reqmain:edit']">单打条码</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Printer"
:disabled="freesingle"
@click="printBackpaper"
v-hasPermi="['system:reqmain:edit']"
>单打回单</el-button>
<el-button type="danger" plain icon="Printer" :disabled="freesingle" @click="printBackpaper"
v-hasPermi="['system:reqmain:edit']">单打回单</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Delete"
@click="cancel"
v-hasPermi="['system:reqmain:export']"
>取消采样</el-button>
<el-button type="warning" plain icon="Delete" @click="cancel"
v-hasPermi="['system:reqmain:export']">取消采样</el-button>
</el-col>
<el-col :span="3">
<barcode-printer
:printData="printBarcodeData"
:disabled="!hasSelectedRows"
type="success"
icon="Printer"
@print-start="showLoading"
@print-success="handlePrintSuccess"
@print-error="handlePrintError"
>
<barcode-printer :printData="printBarcodeData" :disabled="!hasSelectedRows" type="success" icon="Printer"
@print-start="showLoading" @print-success="handlePrintSuccess" @print-error="handlePrintError">
打印条码
</barcode-printer>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table ref="tableRef" class="compact-form my-table" :data="reqmainList" @selection-change="handleSelectionChange" @select="handleSelect" height="530px" :cell-style="tableCellStyle">
<el-table ref="tableRef" class="compact-form my-table" :data="reqmainList" @selection-change="handleSelectionChange"
@select="handleSelect" height="530px" :cell-style="tableCellStyle">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目名称" align="center" prop="sqxmmc" width="200" show-overflow-tooltip/>
<el-table-column label="项目代码" align="center" prop="sqxmdh" width="100" show-overflow-tooltip/>
<el-table-column label="项目名称" align="center" prop="sqxmmc" width="200" show-overflow-tooltip />
<el-table-column label="项目代码" align="center" prop="sqxmdh" width="100" show-overflow-tooltip />
<el-table-column label="项目类别" align="center" prop="cp_xmlb" />
<el-table-column label="样本类型" align="center" prop="yblx" />
<el-table-column label="条码号" align="center" prop="sqh" width="150" show-overflow-tooltip/>
<el-table-column label="条码号" align="center" prop="sqh" width="150" show-overflow-tooltip />
<el-table-column label="申请时间" align="center" prop="sqsj" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ parseTime(scope.row.sqsj, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="病人姓名" align="center" prop="brxm" show-overflow-tooltip/>
<el-table-column label="申请医生" align="center" prop="sqys" :formatter="formatDict('SRD')" show-overflow-tooltip/>
<el-table-column label="序号" align="center" prop="xh" show-overflow-tooltip/>
<el-table-column label="数量" align="center" prop="sl" show-overflow-tooltip/>
<el-table-column label="单价" align="center" prop="dj" show-overflow-tooltip/>
<el-table-column label="状态" align="center" prop="zt" show-overflow-tooltip/>
<el-table-column label="计价" align="center" prop="jjzt" >
<el-table-column label="病人姓名" align="center" prop="brxm" show-overflow-tooltip />
<el-table-column label="申请医生" align="center" prop="sqys" :formatter="formatDict('SRD')" show-overflow-tooltip />
<el-table-column label="序号" align="center" prop="xh" show-overflow-tooltip />
<el-table-column label="数量" align="center" prop="sl" show-overflow-tooltip />
<el-table-column label="单价" align="center" prop="dj" show-overflow-tooltip />
<el-table-column label="状态" align="center" prop="zt" show-overflow-tooltip />
<el-table-column label="计价" align="center" prop="jjzt">
<template #default="scope">
<el-checkbox :model-value="scope.row.jjzt === '1'" disabled>
</el-checkbox>
</template>
</el-table-column>
<el-table-column label="病人代号" align="center" prop="brdh" show-overflow-tooltip/>
<el-table-column label="备注1" align="center" prop="detailBZ1" show-overflow-tooltip/>
<el-table-column label="备注2" align="center" prop="detailBZ2" show-overflow-tooltip/>
<el-table-column label="颜色" align="center" prop="cp_color" v-if="false" show-overflow-tooltip/>
<el-table-column label="确认标识" align="center" prop="cp_cflag" v-if="false" show-overflow-tooltip/>
<el-table-column label="科室" align="center" prop="ksdh" :formatter="formatDict('DP')" show-overflow-tooltip/>
<el-table-column label="病人类型" align="center" prop="brly" :formatter="formatDict('PT')" show-overflow-tooltip/>
<el-table-column label="性别" align="center" prop="brxb" :formatter="formatDict('SX')" show-overflow-tooltip/>
<el-table-column label="病人代号" align="center" prop="brdh" show-overflow-tooltip />
<el-table-column label="备注1" align="center" prop="detailBZ1" show-overflow-tooltip />
<el-table-column label="备注2" align="center" prop="detailBZ2" show-overflow-tooltip />
<el-table-column label="颜色" align="center" prop="cp_color" v-if="false" show-overflow-tooltip />
<el-table-column label="确认标识" align="center" prop="cp_cflag" v-if="false" show-overflow-tooltip />
<el-table-column label="科室" align="center" prop="ksdh" :formatter="formatDict('DP')" show-overflow-tooltip />
<el-table-column label="病人类型" align="center" prop="brly" :formatter="formatDict('PT')" show-overflow-tooltip />
<el-table-column label="性别" align="center" prop="brxb" :formatter="formatDict('SX')" show-overflow-tooltip />
<el-table-column label="生日" align="center" prop="brsr" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ parseTime(scope.row.brsr, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="床号" align="center" prop="ch" width="50" show-overflow-tooltip/>
<el-table-column label="诊断" align="center" prop="zd" width="150" show-overflow-tooltip/>
<el-table-column label="条码类别" align="center" prop="bgddh" v-if="false" show-overflow-tooltip/>
<el-table-column label="年龄" align="center" prop="nl" show-overflow-tooltip/>
<el-table-column label="年龄单位" align="center" prop="nldw" :formatter="formatDict('AU')" show-overflow-tooltip/>
<el-table-column label="采样时间" align="center" prop="cysj" width="180"show-overflow-tooltip>
<el-table-column label="床号" align="center" prop="ch" width="50" show-overflow-tooltip />
<el-table-column label="诊断" align="center" prop="zd" width="150" show-overflow-tooltip />
<el-table-column label="条码类别" align="center" prop="bgddh" v-if="false" show-overflow-tooltip />
<el-table-column label="年龄" align="center" prop="nl" show-overflow-tooltip />
<el-table-column label="年龄单位" align="center" prop="nldw" :formatter="formatDict('AU')" show-overflow-tooltip />
<el-table-column label="采样时间" align="center" prop="cysj" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ parseTime(scope.row.cysj, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
@ -178,8 +141,8 @@
<script setup>
import { ElMessage } from 'element-plus'; // 引入 Element Plus 消息组件
import {querySQD, getReqmain, delReqmain, addReqmain, updateReqmain, printBarcode} from "@/api/mzcx/cydj.js";
import BarcodePrinter from './components/BarcodePrinter.vue';
import { querySQD, getReqmain, delReqmain, addReqmain, updateReqmain, printBarcode } from "@/api/mzcx/cydj.js";
import BarcodePrinter from './BarcodePrinter.vue';
const comDict = inject('comDict');
// 字典数据存储
const dictData = ref({});
@ -198,9 +161,9 @@ const idsnew = computed(() => {
});
const single = ref(true);
const freesingle = ref(true);
const cardtypeOptions = ref([{"label": "就诊卡","value": 1},{"label": "医保卡","value": 2},{"label": "电子医保卡","value": 3},{"label": "身份证", "value": 4}])
const showconfirmOptions = ref([{"label": "未打印", "value": 1},{"label": "已打印", "value": 11}])
const subdayOptions = ref([{"label": "1天","value": 1},{"label": "3天", "value": 3},{"label": "1周","value": 7},{"label": "2周","value": 14},{"label": "1月", "value": 30},{"label": "3月", "value": 90},{"label": "1年","value": 365},{"label": "2年","value": 730}])
const cardtypeOptions = ref([{ "label": "就诊卡", "value": 1 }, { "label": "医保卡", "value": 2 }, { "label": "电子医保卡", "value": 3 }, { "label": "身份证", "value": 4 }])
const showconfirmOptions = ref([{ "label": "未打印", "value": 1 }, { "label": "已打印", "value": 11 }])
const subdayOptions = ref([{ "label": "1天", "value": 1 }, { "label": "3天", "value": 3 }, { "label": "1周", "value": 7 }, { "label": "2周", "value": 14 }, { "label": "1月", "value": 30 }, { "label": "3月", "value": 90 }, { "label": "1年", "value": 365 }, { "label": "2年", "value": 730 }])
const data = reactive({
form: {},
@ -211,7 +174,7 @@ const data = reactive({
klx: null,
zt: 1,
subday: 3000,
cardtype:null
cardtype: null
},
rules: {
brdh: [
@ -254,10 +217,10 @@ const handleSelect = (selection, row, selected) => {
}
};
// 字典格式化方法
const formatDict =(dictType) => {
const formatDict = (dictType) => {
return (row, column, value) => {
// 从全局字典中获取映射值
return dictData.value[dictType].find(item => String(item.value) === String(value).trim())?.label||value ;
return dictData.value[dictType].find(item => String(item.value) === String(value).trim())?.label || value;
};
};
// 定义“空数据”的判断函数
@ -292,14 +255,14 @@ function getDateOffset(days) {
return `${year}-${month}-${day} 00:00:00`;
}
function getSubday() {
const days=0 - queryParams.value.subday;
queryParams.value.stime=getDateOffset(days);
queryParams.value.etime=getDateOffset(1);
const days = 0 - queryParams.value.subday;
queryParams.value.stime = getDateOffset(days);
queryParams.value.etime = getDateOffset(1);
}
/** 查询采样登记列表 */
function getList() {
if(isEmptyData(queryParams.value.brdh)) proxy.$modal.alert("请刷卡或输入门诊号!");
if (isEmptyData(queryParams.value.brdh)) proxy.$modal.alert("请刷卡或输入门诊号!");
else {
loading.value = true;
getSubday();
@ -307,7 +270,7 @@ function getList() {
if (isEmptyData(response.data)) proxy.$modal.alert("没有检索的数据!");
reqmainList.value = response.data;
loading.value = false;
single.value=false;
single.value = false;
// 新增:数据加载完成后触发全选
// 延迟执行(确保DOM已更新)
setTimeout(() => {
@ -322,7 +285,7 @@ function getList() {
// 取消按钮
function cancel() {
// open.value = false;
// reset();
// reset();
}
// 表单重置
@ -367,8 +330,8 @@ function reset() {
zd: null,
cysj: null
};
freesingle.value=false;
single.value=false;
freesingle.value = false;
single.value = false;
proxy.resetForm("reqmainRef");
}
@ -389,7 +352,7 @@ function handleSelectionChange(selection) {
selectedRows.value = selection;
ids.value = selection.map(item => item.sqh);
//console.log('ids:', ids.value);
// console.log('selectedRows:', selectedRows.value);
// console.log('selectedRows:', selectedRows.value);
}
@ -397,13 +360,13 @@ function handleSelectionChange(selection) {
/** 打印选择 */
function printAll(row) {
console.log('idsnew:', idsnew.value);
if (idsnew.value.length=== 0) {
if (idsnew.value.length === 0) {
return;
}
printBarcode({sqh:"20221201167919"}).then(response => {
printBarcode({ sqh: "20221201167919" }).then(response => {
proxy.$modal.msgSuccess("打印成功");
open.value = false;
freesingle.value=false;
freesingle.value = false;
}).catch(() => {
});
}
@ -415,7 +378,7 @@ function printSigne(row) {
apiprintAll(idsnew.value).then(response => {
proxy.$modal.msgSuccess("打印成功");
open.value = false;
freesingle.value=false;
freesingle.value = false;
}).catch(() => {
});
}
@ -449,8 +412,8 @@ const totalPrice = computed(() => {
// 组件挂载时加载字典
onMounted(async () => {
// 加载病人来源字典
const dictRefs = await comDict('PT', 'DP', 'SRD','BT','SX','AU');
// 从 ref 中获取实际数据
const dictRefs = await comDict('PT', 'DP', 'SRD', 'BT', 'SX', 'AU');
// 从 ref 中获取实际数据
dictData.value = {
PT: toRaw(dictRefs.PT.value) || [],
DP: toRaw(dictRefs.DP.value) || [],
@ -562,22 +525,32 @@ const handlePrintError = (errorMsg) => {
.compact-form .el-form-item__label {
padding-bottom: 0px;
}
::v-deep .my-table .el-table__row td {
padding: 1px 0; /* 减小行高 */
padding: 1px 0;
/* 减小行高 */
}
::v-deep .my-table .el-table__header-wrapper th {
padding: 6px 0; /* 表头内边距 */
background-color: #b3d8ff !important; /* 表头背景色(保留之前的设置) */
color: #333; /* 文字颜色加深,提升可读性 */
font-weight: 500; /* 文字加粗 */
padding: 6px 0;
/* 表头内边距 */
background-color: #b3d8ff !important;
/* 表头背景色(保留之前的设置) */
color: #333;
/* 文字颜色加深,提升可读性 */
font-weight: 500;
/* 文字加粗 */
}
::v-deep .my-table .el-table__cell {
padding: 0 2px; /* 减小列间距 */
padding: 0 2px;
/* 减小列间距 */
}
/* 可选:调整表格整体样式 */
::v-deep .my-table {
font-size: 13px; /* 适当减小字体 */
font-size: 13px;
/* 适当减小字体 */
}
::v-deep .my-table .el-table__cell,
@ -585,17 +558,25 @@ const handlePrintError = (errorMsg) => {
border-width: 1px;
border-color: #ebeef5;
}
.card-content {
max-height: 450px; /* 设置最大高度 */
overflow-y: auto; /* 超出高度时显示垂直滚动条 */
padding: 5px; /* 保持与卡片默认一致的内边距 */
max-height: 450px;
/* 设置最大高度 */
overflow-y: auto;
/* 超出高度时显示垂直滚动条 */
padding: 5px;
/* 保持与卡片默认一致的内边距 */
}
.clickable:hover {
cursor: pointer; /* 鼠标悬停时显示手型 */
transition: all 0.3s; /* 平滑过渡效果 */
cursor: pointer;
/* 鼠标悬停时显示手型 */
transition: all 0.3s;
/* 平滑过渡效果 */
color: blue;
}
/* 汇总行样式 */
.table-summary {
display: flex;
@ -607,7 +588,9 @@ const handlePrintError = (errorMsg) => {
border: 1px solid #ebeef5;
border-radius: 4px;
font-weight: 500;
line-height: 24px; /* 汇总行文字行高 */
line-height: 24px;
/* 汇总行文字行高 */
.summary-item {
display: flex;
align-items: center;
@ -621,12 +604,16 @@ const handlePrintError = (errorMsg) => {
.summary-value {
color: #303133;
min-width: 40px;
text-align: left;/* 核心:数值左对齐 */
text-align: left;
/* 核心:数值左对齐 */
}
/* 单独设置"单价合计"的值为红色 */
&:nth-child(3) .summary-value {
color: #f56c6c; /* Element UI 红色主题色 */
font-weight: 600; /* 可选:加粗字体 */
color: #f56c6c;
/* Element UI 红色主题色 */
font-weight: 600;
/* 可选:加粗字体 */
}
}
}

View File

@ -1,44 +1,41 @@
<template>
<div class="app-container">
<el-row :gutter="5" class="compact-form" :span="24" >
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-position="left" >
<el-row :gutter="5" class="compact-form" :span="24">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-position="left">
<el-col :span="24" :xs="24">
<el-form-item label="" label-width="0px" prop="brdh" >
<el-form-item label="" label-width="0px" prop="brdh">
<el-col :span="6">
<div> 就诊卡号/病历号/磁卡号: </div>
<el-input
v-model="queryParams.brdh"
placeholder="就诊卡号/病历号/磁卡号"
clearable
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.brdh" placeholder="就诊卡号/病历号/磁卡号" clearable @keyup.enter="handleQuery" />
</el-col>
<el-col :span="2">
<el-radio-group v-model="queryParams.zt" size="default">
<el-radio v-for="(item, index) in showconfirmOptions" :key="index" :label="item.value"
:disabled="item.disabled">{{item.label}}</el-radio>
:disabled="item.disabled">{{ item.label }}</el-radio>
</el-radio-group>
</el-col>
<el-col :span="1"> <div> 申请获取期限: </div></el-col>
<el-col :span="1">
<div> 申请获取期限: </div>
</el-col>
<el-col :span="6">
<el-radio-group v-model="queryParams.subday" size="default">
<el-radio v-for="(item, index) in subdayOptions" :key="index" :label="item.value"
:disabled="item.disabled">{{item.label}}</el-radio>
:disabled="item.disabled">{{ item.label }}</el-radio>
</el-radio-group>
</el-col>
<el-col :span="2">
<el-select v-model="queryParams.cardtype" placeholder="卡类型" clearable :style="{width: '100%'}">
<el-option v-for="(item, index) in cardtypeOptions" :key="index" :label="item.label"
:value="item.value" :disabled="item.disabled"></el-option>
<el-select v-model="queryParams.cardtype" placeholder="卡类型" clearable :style="{ width: '100%' }">
<el-option v-for="(item, index) in cardtypeOptions" :key="index" :label="item.label" :value="item.value"
:disabled="item.disabled"></el-option>
</el-select>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="Search" @click="readCard">读卡</el-button>
</el-col>
<el-col :span="1.5">
<PatSearchDialog @select="handlePatientSelect"/>
<PatSearchDialog @select="handlePatientSelect" />
</el-col>
</el-form-item>
</el-col>
@ -47,98 +44,71 @@
</el-row>
<el-row :gutter="5" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Search"
@click="handleQuery"
v-hasPermi="['system:reqmain:Search']"
>查询</el-button>
<el-button type="primary" plain icon="Search" @click="handleQuery"
v-hasPermi="['system:reqmain:Search']">查询</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Printer"
:disabled="single"
@click="printAll"
v-hasPermi="['system:reqmain:add']"
>打印</el-button>
<el-button type="success" plain icon="Printer" :disabled="single" @click="printAll"
v-hasPermi="['system:reqmain:add']">打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Printer"
:disabled="single"
@click="printSigne"
v-hasPermi="['system:reqmain:edit']"
>单打条码</el-button>
<el-button type="success" plain icon="Printer" :disabled="single" @click="printSigne"
v-hasPermi="['system:reqmain:edit']">单打条码</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Printer"
:disabled="freesingle"
@click="printBack"
v-hasPermi="['system:reqmain:edit']"
>单打回单</el-button>
<el-button type="danger" plain icon="Printer" :disabled="freesingle" @click="printBack"
v-hasPermi="['system:reqmain:edit']">单打回单</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Delete"
@click="cancel"
v-hasPermi="['system:reqmain:export']"
>取消采样</el-button>
<el-button type="warning" plain icon="Delete" @click="cancel"
v-hasPermi="['system:reqmain:export']">取消采样</el-button>
</el-col>
</el-row>
<el-row :gutter="5" class="table-container">
<el-table ref="tableRef" class="compact-form my-table" :data="reqmainList" @selection-change="handleSelectionChange" @select="handleSelect" height="100%" :cell-style="tableCellStyle">
<el-table ref="tableRef" class="compact-form my-table" :data="reqmainList"
@selection-change="handleSelectionChange" @select="handleSelect" height="100%" :cell-style="tableCellStyle">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目名称" align="center" prop="sqxmmc" width="200" show-overflow-tooltip/>
<el-table-column label="项目代码" align="center" prop="sqxmdh" width="100" show-overflow-tooltip/>
<el-table-column label="项目名称" align="center" prop="sqxmmc" width="200" show-overflow-tooltip />
<el-table-column label="项目代码" align="center" prop="sqxmdh" width="100" show-overflow-tooltip />
<el-table-column label="项目类别" align="center" prop="cp_xmlb" />
<el-table-column label="样本类型" align="center" prop="yblx" />
<el-table-column label="条码号" align="center" prop="sqh" width="150" show-overflow-tooltip/>
<el-table-column label="条码号" align="center" prop="sqh" width="150" show-overflow-tooltip />
<el-table-column label="申请时间" align="center" prop="sqsj" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ parseTime(scope.row.sqsj, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="病人姓名" align="center" prop="brxm" show-overflow-tooltip/>
<el-table-column label="申请医生" align="center" prop="sqys" :formatter="formatDict('SRD')" show-overflow-tooltip/>
<el-table-column label="序号" align="center" prop="xh" show-overflow-tooltip/>
<el-table-column label="数量" align="center" prop="sl" show-overflow-tooltip/>
<el-table-column label="单价" align="center" prop="dj" show-overflow-tooltip/>
<el-table-column label="状态" align="center" prop="zt" show-overflow-tooltip/>
<el-table-column label="计价" align="center" prop="jjzt" >
<el-table-column label="病人姓名" align="center" prop="brxm" show-overflow-tooltip />
<el-table-column label="申请医生" align="center" prop="sqys" :formatter="formatDict('SRD')" show-overflow-tooltip />
<el-table-column label="序号" align="center" prop="xh" show-overflow-tooltip />
<el-table-column label="数量" align="center" prop="sl" show-overflow-tooltip />
<el-table-column label="单价" align="center" prop="dj" show-overflow-tooltip />
<el-table-column label="状态" align="center" prop="zt" show-overflow-tooltip />
<el-table-column label="计价" align="center" prop="jjzt">
<template #default="scope">
<el-checkbox :model-value="scope.row.jjzt === '1'" disabled>
</el-checkbox>
</template>
</el-table-column>
<el-table-column label="病人代号" align="center" prop="brdh" show-overflow-tooltip/>
<el-table-column label="备注1" align="center" prop="detailBZ1" show-overflow-tooltip/>
<el-table-column label="备注2" align="center" prop="detailBZ2" show-overflow-tooltip/>
<el-table-column label="颜色" align="center" prop="cp_color" v-if="false" show-overflow-tooltip/>
<el-table-column label="确认标识" align="center" prop="cp_cflag" v-if="false" show-overflow-tooltip/>
<el-table-column label="科室" align="center" prop="ksdh" :formatter="formatDict('DP')" show-overflow-tooltip/>
<el-table-column label="病人类型" align="center" prop="brly" :formatter="formatDict('PT')" show-overflow-tooltip/>
<el-table-column label="性别" align="center" prop="brxb" :formatter="formatDict('SX')" show-overflow-tooltip/>
<el-table-column label="病人代号" align="center" prop="brdh" show-overflow-tooltip />
<el-table-column label="备注1" align="center" prop="detailBZ1" show-overflow-tooltip />
<el-table-column label="备注2" align="center" prop="detailBZ2" show-overflow-tooltip />
<el-table-column label="颜色" align="center" prop="cp_color" v-if="false" show-overflow-tooltip />
<el-table-column label="确认标识" align="center" prop="cp_cflag" v-if="false" show-overflow-tooltip />
<el-table-column label="科室" align="center" prop="ksdh" :formatter="formatDict('DP')" show-overflow-tooltip />
<el-table-column label="病人类型" align="center" prop="brly" :formatter="formatDict('PT')" show-overflow-tooltip />
<el-table-column label="性别" align="center" prop="brxb" :formatter="formatDict('SX')" show-overflow-tooltip />
<el-table-column label="生日" align="center" prop="brsr" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ parseTime(scope.row.brsr, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="床号" align="center" prop="ch" width="50" show-overflow-tooltip/>
<el-table-column label="诊断" align="center" prop="zd" width="150" show-overflow-tooltip/>
<el-table-column label="条码类别" align="center" prop="bgddh" v-if="false" show-overflow-tooltip/>
<el-table-column label="年龄" align="center" prop="nl" show-overflow-tooltip/>
<el-table-column label="年龄单位" align="center" prop="nldw" :formatter="formatDict('AU')" show-overflow-tooltip/>
<el-table-column label="采样时间" align="center" prop="cysj" width="180"show-overflow-tooltip>
<el-table-column label="床号" align="center" prop="ch" width="50" show-overflow-tooltip />
<el-table-column label="诊断" align="center" prop="zd" width="150" show-overflow-tooltip />
<el-table-column label="条码类别" align="center" prop="bgddh" v-if="false" show-overflow-tooltip />
<el-table-column label="年龄" align="center" prop="nl" show-overflow-tooltip />
<el-table-column label="年龄单位" align="center" prop="nldw" :formatter="formatDict('AU')" show-overflow-tooltip />
<el-table-column label="采样时间" align="center" prop="cysj" width="180" show-overflow-tooltip>
<template #default="scope">
<span>{{ parseTime(scope.row.cysj, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
@ -172,6 +142,7 @@ import {
printBarcodeall
} from "@/api/mzcx/cydj.js";
import PatSearchDialog from './components/OutPatList.vue'
import { classCom } from '@/utils/classCom';
const comDict = inject('comDict');
// 字典数据存储
const dictData = ref({});
@ -190,9 +161,9 @@ const idsnew = computed(() => {
});
const single = ref(true);
const freesingle = ref(true);
const cardtypeOptions = ref([{"label": "就诊卡","value": 1},{"label": "医保卡","value": 2},{"label": "电子医保卡","value": 3},{"label": "身份证", "value": 4}])
const showconfirmOptions = ref([{"label": "未打印", "value": 1},{"label": "已打印", "value": 11}])
const subdayOptions = ref([{"label": "1天","value": 1},{"label": "3天", "value": 3},{"label": "1周","value": 7},{"label": "2周","value": 14},{"label": "1月", "value": 30},{"label": "3月", "value": 90},{"label": "1年","value": 365},{"label": "2年","value": 730}])
const cardtypeOptions = ref([{ "label": "就诊卡", "value": 1 }, { "label": "医保卡", "value": 2 }, { "label": "电子医保卡", "value": 3 }, { "label": "身份证", "value": 4 }])
const showconfirmOptions = ref([{ "label": "未打印", "value": 1 }, { "label": "已打印", "value": 11 }])
const subdayOptions = ref([{ "label": "1天", "value": 1 }, { "label": "3天", "value": 3 }, { "label": "1周", "value": 7 }, { "label": "2周", "value": 14 }, { "label": "1月", "value": 30 }, { "label": "3月", "value": 90 }, { "label": "1年", "value": 365 }, { "label": "2年", "value": 730 }])
const data = reactive({
form: {},
@ -203,7 +174,7 @@ const data = reactive({
klx: null,
zt: 1,
subday: 3000,
cardtype:null
cardtype: null
},
rules: {
brdh: [
@ -246,10 +217,10 @@ const handleSelect = (selection, row, selected) => {
}
};
// 字典格式化方法
const formatDict =(dictType) => {
const formatDict = (dictType) => {
return (row, column, value) => {
// 从全局字典中获取映射值
return dictData.value[dictType].find(item => String(item.value) === String(value).trim())?.label||value ;
return dictData.value[dictType].find(item => String(item.value) === String(value).trim())?.label || value;
};
};
// 定义“空数据”的判断函数
@ -284,14 +255,14 @@ function getDateOffset(days) {
return `${year}-${month}-${day} 00:00:00`;
}
function getSubday() {
const days=0 - queryParams.value.subday;
queryParams.value.stime=getDateOffset(days);
queryParams.value.etime=getDateOffset(1);
const days = 0 - queryParams.value.subday;
queryParams.value.stime = getDateOffset(days);
queryParams.value.etime = getDateOffset(1);
}
/** 查询采样登记列表 */
function getList() {
freesingle.value=true;
if(isEmptyData(queryParams.value.brdh)) proxy.$modal.alert("请刷卡或输入门诊号!");
freesingle.value = true;
if (isEmptyData(queryParams.value.brdh)) proxy.$modal.alert("请刷卡或输入门诊号!");
else {
loading.value = true;
getSubday();
@ -300,7 +271,7 @@ function getList() {
reqmainList.value = response.data;
// console.log('response:', response.data);
loading.value = false;
single.value=false;
single.value = false;
// 新增:数据加载完成后触发全选
// 延迟执行(确保DOM已更新)
setTimeout(() => {
@ -315,7 +286,7 @@ function getList() {
// 取消按钮
function cancel() {
// open.value = false;
// reset();
// reset();
}
// 表单重置
@ -360,8 +331,8 @@ function reset() {
zd: null,
cysj: null
};
freesingle.value=false;
single.value=false;
freesingle.value = false;
single.value = false;
proxy.resetForm("reqmainRef");
}
@ -377,7 +348,7 @@ function readCard() {
// 处理病人选择事件
const handlePatientSelect = (patient) => {
// console.log('选中的病人:', patient);
queryParams.value.brdh=patient
queryParams.value.brdh = patient
// 处理选中的病人数据
handleQuery();
};
@ -404,7 +375,7 @@ function printSigne(row) {
printBarcodeall(idsnew.value).then(response => {
proxy.$modal.msgSuccess("打印成功");
open.value = false;
freesingle.value=false;
freesingle.value = false;
}).catch(() => {
});
}
@ -421,7 +392,7 @@ function printBack(row) {
open.value = false;
}).catch(() => {
});
}
}
const totalRows = computed(() => {
@ -439,8 +410,8 @@ const totalPrice = computed(() => {
// 组件挂载时加载字典
onMounted(async () => {
// 加载病人来源字典
const dictRefs = await comDict('PT', 'DP', 'SRD','BT','SX','AU');
// 从 ref 中获取实际数据
const dictRefs = await comDict('PT', 'DP', 'SRD', 'BT', 'SX', 'AU');
// 从 ref 中获取实际数据
dictData.value = {
PT: toRaw(dictRefs.PT.value) || [],
DP: toRaw(dictRefs.DP.value) || [],
@ -458,9 +429,9 @@ const tableCellStyle = ({ row, column }) => {
// 只对“项目类别”列生效
if (column.label === '项目类别') {
// 1. 转换颜色(十进制→十六进制)
const bgColor = decimalToHexColor(row.cp_color);
const bgColor = classCom.decimalToHexColor(row.cp_color);
// 2. 自动计算文字颜色(确保和背景色对比度足够)
const textColor = getContrastTextColor(bgColor);
const textColor = classCom.getContrastTextColor(bgColor);
return {
backgroundColor: bgColor, // 背景色(转换后的值)
@ -472,39 +443,6 @@ const tableCellStyle = ({ row, column }) => {
return {}; // 其他列保持默认样式
};
// 十进制颜色值转十六进制颜色码(适配BGR转RGB)
const decimalToHexColor = (decimal) => {
if (!decimal && decimal !== 0) return '#FFFFFF'; // 空值默认白色
// 1. 十进制转十六进制(去除前缀0x,大写)
let hex = parseInt(decimal, 10).toString(16).toUpperCase();
// 2. 不足6位则前面补0(确保是6位)
if (hex.length < 6) {
hex = hex.padStart(6, '0'); // 例如:192→"C0"→补0为"0000C0"
}
// 3. BGR转RGB(反转字节顺序:前两位和后两位交换)
// 例:80FFFF → 拆分为80、FF、FF → 反转后FF、FF、80 → FFFF80
const r = hex.substring(4, 6); // 取后两位
const g = hex.substring(2, 4); // 取中间两位
const b = hex.substring(0, 2); // 取前两位
const rgbHex = r + g + b;
return `#${rgbHex}`; // 最终颜色码
};
// 辅助函数:根据背景色计算文字颜色(黑/白)
const getContrastTextColor = (bgColor) => {
// 提取RGB值(如#FFFF80 → R=255, G=255, B=128)
const r = parseInt(bgColor.slice(1, 3), 16);
const g = parseInt(bgColor.slice(3, 5), 16);
const b = parseInt(bgColor.slice(5, 7), 16);
// 计算亮度(标准公式)
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// 亮度>0.5用黑色,否则用白色(确保清晰)
return luminance > 0.5 ? '#333333' : '#FFFFFF';
};
</script>
@ -512,21 +450,31 @@ const getContrastTextColor = (bgColor) => {
.app-container {
display: flex;
flex-direction: column;
height: 90vh; /* 容器高度等于窗口高度 */
height: 90vh;
/* 容器高度等于窗口高度 */
overflow: hidden;
}
.compact-form {
height: 75px; /* 固定高度 */
height: 75px;
/* 固定高度 */
}
.mb8 {
height: 30px; /* 固定高度 */
height: 30px;
/* 固定高度 */
}
.table-summary{
height: 10px; /* 固定高度 */
.table-summary {
height: 10px;
/* 固定高度 */
}
.table-container {
flex: 1; /* 占满剩余高度 */
overflow: hidden; /* 避免表格超出容器 */
flex: 1;
/* 占满剩余高度 */
overflow: hidden;
/* 避免表格超出容器 */
}
.compact-form .el-form-item {
@ -537,22 +485,32 @@ const getContrastTextColor = (bgColor) => {
.compact-form .el-form-item__label {
padding-bottom: 0px;
}
::v-deep .my-table .el-table__row td {
padding: 1px 0; /* 减小行高 */
padding: 1px 0;
/* 减小行高 */
}
::v-deep .my-table .el-table__header-wrapper th {
padding: 6px 0; /* 表头内边距 */
background-color: #b3d8ff !important; /* 表头背景色(保留之前的设置) */
color: #333; /* 文字颜色加深,提升可读性 */
font-weight: 500; /* 文字加粗 */
padding: 6px 0;
/* 表头内边距 */
background-color: #b3d8ff !important;
/* 表头背景色(保留之前的设置) */
color: #333;
/* 文字颜色加深,提升可读性 */
font-weight: 500;
/* 文字加粗 */
}
::v-deep .my-table .el-table__cell {
padding: 0 2px; /* 减小列间距 */
padding: 0 2px;
/* 减小列间距 */
}
/* 可选:调整表格整体样式 */
::v-deep .my-table {
font-size: 13px; /* 适当减小字体 */
font-size: 13px;
/* 适当减小字体 */
}
::v-deep .my-table .el-table__cell,
@ -560,17 +518,25 @@ const getContrastTextColor = (bgColor) => {
border-width: 1px;
border-color: #ebeef5;
}
.card-content {
max-height: 450px; /* 设置最大高度 */
overflow-y: auto; /* 超出高度时显示垂直滚动条 */
padding: 5px; /* 保持与卡片默认一致的内边距 */
max-height: 450px;
/* 设置最大高度 */
overflow-y: auto;
/* 超出高度时显示垂直滚动条 */
padding: 5px;
/* 保持与卡片默认一致的内边距 */
}
.clickable:hover {
cursor: pointer; /* 鼠标悬停时显示手型 */
transition: all 0.3s; /* 平滑过渡效果 */
cursor: pointer;
/* 鼠标悬停时显示手型 */
transition: all 0.3s;
/* 平滑过渡效果 */
color: blue;
}
/* 汇总行样式 */
.table-summary {
display: flex;
@ -582,7 +548,9 @@ const getContrastTextColor = (bgColor) => {
border: 1px solid #ebeef5;
border-radius: 4px;
font-weight: 500;
line-height: 24px; /* 汇总行文字行高 */
line-height: 24px;
/* 汇总行文字行高 */
.summary-item {
display: flex;
align-items: center;
@ -596,12 +564,16 @@ const getContrastTextColor = (bgColor) => {
.summary-value {
color: #303133;
min-width: 40px;
text-align: left;/* 核心:数值左对齐 */
text-align: left;
/* 核心:数值左对齐 */
}
/* 单独设置"单价合计"的值为红色 */
&:nth-child(3) .summary-value {
color: #f56c6c; /* Element UI 红色主题色 */
font-weight: 600; /* 可选:加粗字体 */
color: #f56c6c;
/* Element UI 红色主题色 */
font-weight: 600;
/* 可选:加粗字体 */
}
}
}

View File

@ -0,0 +1,561 @@
<template>
<div class="rule-management-container">
<!-- 顶部操作栏 -->
<div class="mb20">
<el-button type="primary" icon="Search" @click="handleQuery"> 查询</el-button>
<el-button type="success" icon="Plus" @click="addgz">新增</el-button>
<el-button type="primary" icon="Select" @click="saveHandle">保存</el-button>
<!-- :disabled="flag" -->
<el-button icon="Plus" @click="addDetail">新增明细</el-button>
</div>
<!-- 左右分栏区域 -->
<div class="content-wrapper">
<!-- 左侧表格 -->
<div class="left-table-area">
<el-table :data="ruleList" @row-click="handleRowClick" border style="width: 100%" highlight-current-row
class="custom-table">
<el-table-column prop="bglqdh" label="规则代号" width="100" />
<el-table-column prop="bglqmc" label="规则名称" width="200" />
<el-table-column prop="bz" label="备注" />
<el-table-column label="操作" align="center" fixed="right" width="100">
<template #default="scope">
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 右侧表单与明细表格 -->
<div class="right-form-area">
<el-form :model="currentRule" label-width="80px" class="rule-form">
<el-form-item label="规则代号">
<el-input v-model="currentRule.bglqdh" disabled />
</el-form-item>
<el-form-item label="规则名称">
<el-input v-model="currentRule.bglqmc" @blur="editHandle(currentRule, 'bglqmc')"
@keyup.enter="editHandle(currentRule, 'bglqmc')" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="currentRule.bz" :rows="2" @blur="editHandle(currentRule, 'bz')"
@keyup.enter="editHandle(currentRule, 'bz')" />
</el-form-item>
</el-form>
<div class="detail-table-header">
<span>规则明细</span>
</div>
<el-table :data="detailList" border style="width: 100%;" class="detail-table" :cell-class-name="cellClassName">
<el-table-column prop="weekday" label="起始日期" width="100">
<template #default="scope">
<el-select v-model="scope.row.weekday" placeholder="选择星期" size="small" class="week-select"
@change="handleDetailChange(scope.row)">
<el-option v-for="day in weekDays" :key="day.value" :label="day.label" :value="day.value" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="weekday1" label="截止日期" width="100">
<template #default="scope">
<el-select v-model="scope.row.weekday1" placeholder="选择星期" size="small" class="week-select"
@change="handleDetailChange(scope.row)">
<el-option v-for="day in weekDays" :key="day.value" :label="day.label" :value="day.value" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="kssj" label="起始时间" width="100">
<template #default="scope">
<el-input v-model="scope.row.kssj" placeholder="HH:mm" size="small"
@input="handleTimeInput(scope.row, 'kssj')" maxlength="5" class="time-input" @focus="handleInputFocus"
@blur="handleInputBlur(scope.row, 'kssj')" />
</template>
</el-table-column>
<el-table-column prop="jssj" label="截止时间" width="100">
<template #default="scope">
<el-input v-model="scope.row.jssj" placeholder="HH:mm" size="small"
@input="handleTimeInput(scope.row, 'jssj')" maxlength="5" class="time-input" @focus="handleInputFocus"
@blur="handleInputBlur(scope.row, 'jssj')" />
</template>
</el-table-column>
<el-table-column prop="bglqsm" label="报告领取声明">
<template #default="scope">
<el-input v-model="scope.row.bglqsm" size="small" class="statement-input" @focus="handleInputFocus" />
</template>
</el-table-column>
<el-table-column prop="bgxss" label="所需小时数" width="100">
<template #default="scope">
<el-input v-model="scope.row.bgxss" placeholder="0" size="small"
@input="handleNumberInput(scope.row, 'bgxss')" class="number-input" @focus="handleInputFocus" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button size="small" type="danger" @click="delDetail(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { rptgetruleList, rptgetruleadd, rptgetruleUpdate, rptgetruleDel, rptgetruleDetail, rptgetruleDelDetail } from '@/api/mzcx/index';
import { ElMessageBox, ElMessage } from 'element-plus'
// 星期选项数据
const weekDays = [
{ label: '周一', value: '1' },
{ label: '周二', value: '2' },
{ label: '周三', value: '3' },
{ label: '周四', value: '4' },
{ label: '周五', value: '5' },
{ label: '周六', value: '6' },
{ label: '周日', value: '7' }
]
interface Rule {
bglqdh: number,
bglqmc: string,
bz: string,
status: boolean,
originalData: object,
addFlag: boolean
}
// 模拟规则列表数据
const ruleList = ref<Rule[]>([])
// 当前选中的规则数据
const currentRule = ref({
bglqdh: 1,
bglqmc: '',
bz: '',
})
interface Detail {
weekday: string,
weekday1: string,
kssj: string,
jssj: string,
bglqsm: string,
bgxss: number,
bglqdh: number,
xh: number,
detaiFlag: boolean
status: boolean // 新增:标识明细是否被修改
}
const detailList = ref<Detail[]>([])
const rptgetruledetail = ref<Detail[]>([])
// 是否正在编辑输入框
const isEditing = ref(false)
const handleQuery = () => {
getList()
}
// 处理时间输入,限制为HH:mm格式
const handleTimeInput = (row: any, field: string) => {
// 移除非数字和非冒号字符
let value = row[field].replace(/[^0-9:]/g, '')
// 自动添加冒号
if (value.length === 2 && !value.includes(':')) {
value += ':'
}
row[field] = value
}
// 处理时间输入失去焦点时的格式化
const handleInputBlur = (row: any, field: string) => {
isEditing.value = false
let value = row[field]
// 补全时间格式
if (field === 'kssj' || field === 'jssj') {
if (value.length === 1) {
value = `0${value}:00`
} else if (value.length === 2 && !value.includes(':')) {
value += ':00'
} else if (value.length === 3 && value.includes(':')) {
value += '0'
}
// 验证小时和分钟范围
if (value.includes(':')) {
const [hours, minutes] = value.split(':')
const validHours = Math.min(Math.max(parseInt(hours) || 0, 0), 23).toString().padStart(2, '0')
const validMinutes = Math.min(Math.max(parseInt(minutes) || 0, 0), 59).toString().padStart(2, '0')
value = `${validHours}:${validMinutes}`
} else {
value = '00:00'
}
row[field] = value
row.status = true;
}
czList()
}
// 处理数字输入,限制为数字
const handleNumberInput = (row: any, field: string) => {
// 只保留数字和小数点
row[field] = row[field].replace(/[^0-9.]/g, '')
// 确保只有一个小数点
const dotIndex = row[field].indexOf('.')
if (dotIndex !== -1 && row[field].lastIndexOf('.') !== dotIndex) {
row[field] = row[field].slice(0, row[field].lastIndexOf('.'))
row.status = true;
}
czList()
}
// 处理明细变化
const handleDetailChange = (row: any) => {
row.status = true;
czList()
}
// 输入框获得焦点
const handleInputFocus = () => {
isEditing.value = true
}
// 单元格样式类
const cellClassName = () => {
return 'no-edit-cell'
}
// 行点击事件,实现右侧联动
const handleRowClick = (row: any) => {
const { bglqdh, bglqmc, bz } = row
currentRule.value = row
row.originalData = JSON.parse(JSON.stringify(row));
getgzList(row)
}
// 新增规则
const addgz = () => {
ruleList.value.unshift(
{
bglqdh: Math.max(...ruleList.value.map((item: any) => item.bglqdh), 0) + 1,
bglqmc: 'test',
bz: '',
status: true,
originalData: {},
addFlag: true
}
)
currentRule.value = ruleList.value[0]
detailList.value = [
{
bglqdh: Math.max(...ruleList.value.map((item: any) => item.bglqdh), 0),
weekday: '1',
weekday1: '2',
kssj: '09:00',
jssj: '18:00',
bglqsm: '领取声明',
bgxss: 1,
xh: detailList.value.length,
detaiFlag: true,
status: false
}
]
rptgetruledetail.value.push(...detailList.value)
}
// 新增规则明细
const addDetail = () => {
detailList.value.push({
bglqdh: currentRule.value.bglqdh,
weekday: '1',
weekday1: '2',
kssj: '09:00',
jssj: '18:00',
bglqsm: '领取声明',
bgxss: 1,
xh: detailList.value.length + 1,
detaiFlag: false,
status: false
})
ruleList.value.forEach((item: any) => {
if (item.bglqdh == currentRule.value.bglqdh) {
item.status = true
}
});
rptgetruledetail.value.push(...detailList.value)
}
const editHandle = (row: any, field: string) => {
ruleList.value.forEach((item: any) => {
if (item.bglqdh == row.bglqdh) {
item[field] = row[field]
item.status = JSON.stringify(row[field]) !== JSON.stringify(item.originalData[field]);
}
});
czList()
}
const czList = () => {
ruleList.value.forEach((item: any) => {
if (item.bglqdh == currentRule.value.bglqdh) {
item.status = true
}
});
rptgetruledetail.value = rptgetruledetail.value.filter((item: any) => item.bglqdh !== currentRule.value.bglqdh)
rptgetruledetail.value.push(...detailList.value)
}
// 保存
const saveHandle = () => {
const addList = ruleList.value.filter((item: any) => item.addFlag)
const updateList = ruleList.value.filter((item: any) => item.status && !item.addFlag)
const addDetailList = rptgetruledetail.value.filter((item: any) => item.detaiFlag)
const upadteDetailList = rptgetruledetail.value.filter((item: any) => !item.detaiFlag)
// 保存成功后的回调处理
const handleSuccess = () => {
// 重置规则的新增/修改状态
ruleList.value.forEach(item => {
item.addFlag = false;
item.status = false;
});
// 重置明细的新增/修改状态
detailList.value.forEach(item => {
item.detaiFlag = false;
item.status = false;
});
ElMessage.success('保存成功');
};
if (updateList.length > 0) {
rptgetruleUpdate({ ruleList: updateList, ruleDetailList: upadteDetailList }).then((res: any) => {
if (res.code == 0) {
handleSuccess()
}
})
} if (addList.length > 0) {
rptgetruleadd({ ruleList: addList, ruleDetailList: addDetailList }).then((res: any) => {
if (res.code == 0) {
handleSuccess()
}
})
}
}
// 删除规则
const handleDelete = (row: any) => {
ElMessageBox.confirm(
`确定删除${row.bglqdh}数据吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
const handleSueess = () => {
ruleList.value = ruleList.value.filter((item: any) => item.bglqdh !== row.bglqdh)
handleRowClick(ruleList.value[0])
};
if (row.addFlag) {
handleSueess()
} else {
rptgetruleDel({ bglqdh: row.bglqdh }).then((res: any) => {
if (res.code == 0) {
ElMessage.success('删除成功');
handleSueess()
}
})
}
})
.catch(() => {
})
};
// 删除规则明细
const delDetail = (row: any) => {
ElMessageBox.confirm(
`确定删除该项数据吗?`, "提示",
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
const handleSueess = () => {
detailList.value = detailList.value.filter((item: any) => item.xh !== row.xh)
};
if (row.detaiFlag) {
handleSueess()
} else {
rptgetruleDelDetail({ bglqdh: row.bglqdh, xh: row.xh }).then((res: any) => {
if (res.code == 0) {
ElMessage.success('删除成功');
handleSueess()
}
})
}
})
.catch(() => {
})
};
const getList = () => {
rptgetruleList().then((res: any) => {
if (res.code == 0) {
ruleList.value = res.data
ruleList.value.forEach((item: any) => {
item.originalData = {}
item.status = false
})
handleRowClick(ruleList.value[0])
getgzList(res.data[0])
}
})
}
const getgzList = (info: any) => {
if (info.status) {
detailList.value = rptgetruledetail.value.filter((item: any) => item.bglqdh == info.bglqdh)
} else {
rptgetruleDetail({ bglqdh: info.bglqdh }).then((res: any) => {
if (res.code == 0) {
detailList.value = res.data
}
})
}
}
getList()
// 初始化默认选中第一行
onMounted(() => {
})
// 替换原flag的定义,改为计算属性
const flag = computed(() => {
// 检查是否有新增规则
const hasNewRule = ruleList.value.some(item => item.addFlag);
// 检查是否有修改的规则
const hasUpdatedRule = ruleList.value.some(item => item.status);
// 检查是否有新增明细
const hasNewDetail = detailList.value.some(item => item.detaiFlag);
// 检查是否有修改的明细
const hasUpdatedDetail = detailList.value.some(item => item.status);
// 当存在任何新增或修改时,按钮可点击(返回false),否则禁用(返回true)
return !(hasNewRule || hasUpdatedRule || hasNewDetail || hasUpdatedDetail);
});
</script>
<style scoped>
.rule-management-container {
height: 90vh;
/* display: flex;
flex-direction: column; */
padding: 10px;
box-sizing: border-box;
/* background-color: #f0f2f5; */
}
.top-toolbar {
padding: 8px 15px;
background-color: #FFF;
border-radius: 4px;
margin-bottom: 10px;
}
/* ::v-deep .top-toolbar .el-button {
margin-right: 8px;
} */
.content-wrapper {
flex: 1;
display: flex;
gap: 10px;
height: calc(100% - 100px);
}
.left-table-area {
width: 50%;
background-color: #fff;
border: 1px solid #e6e6e6;
border-radius: 4px;
padding: 5px;
display: flex;
flex-direction: column;
}
.right-form-area {
width: 50%;
background-color: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
padding: 15px;
display: flex;
flex-direction: column;
}
.rule-form {
background-color: #fff;
padding: 15px;
border-radius: 4px;
border: 1px solid #d9d9d9;
margin-bottom: 15px;
}
.detail-table-header {
padding: 8px 0;
font-weight: 500;
color: #1890ff;
border-bottom: 1px solid #91d5ff;
margin-bottom: 5px;
}
::v-deep .custom-table th {
background-color: #f5f7fa;
}
::v-deep .detail-table th {
background-color: #f0faff;
}
::v-deep .week-select {
width: 100%;
}
::v-deep .el-table .el-table__cell {
padding: 5px 0;
}
::v-deep .el-input__inner {
padding: 5px 10px;
height: 32px;
}
/* 禁用单元格点击效果 */
/* ::v-deep .no-edit-cell {
pointer-events: none;
} */
/* 让输入框可以被点击 */
::v-deep .no-edit-cell .el-input,
::v-deep .no-edit-cell .el-select {
pointer-events: auto;
}
::v-deep .time-input,
::v-deep .number-input {
width: 100%;
}
::v-deep .statement-input {
width: 100%;
}
</style>

View File

@ -0,0 +1,337 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!--部门数据-->
<el-col :span="4" :xs="24">
<div>
<el-input v-model="deptName" placeholder="请输入部门名称" clearable prefix-icon="Search"
style="margin-bottom: 20px" />
</div>
<div class="head-container">
<el-tree :data="deptOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false"
:filter-node-method="filterNode" ref="deptTreeRef" node-key="id" highlight-current default-expand-all
@node-click="handleNodeClick" />
</div>
</el-col>
<!--用户数据-->
<el-col :span="20" :xs="24">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="仪器" prop="yq">
<el-input v-model="queryParams.yq" placeholder="请输入仪器" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="参数代号" prop="xxdh">
<el-input v-model="queryParams.xxdh" placeholder="请输入参数代号" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="参数值" prop="xxqz">
<el-input v-model="queryParams.xxqz" placeholder="请输入参数值" clearable style="width: 240px"
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col> -->
<!-- <el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col> -->
<!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar> -->
</el-row>
<el-table v-loading="loading" :data="tableData" height="calc(100vh - 300px)">
<!-- <el-table-column type="selection" width="50" align="center" /> -->
<el-table-column label="仪器" align="center" prop="yq" />
<el-table-column label="代号" align="center" prop="xxdh" />
<el-table-column label="类别" align="center" prop="xxlb" />
<el-table-column label="参数值" align="center" prop="xxqz" />
<el-table-column label="备注" align="center" prop="bz" width="120" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="text" plain icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button type="text" plain icon="delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-col>
</el-row>
<!-- 添加或修改用户配置对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form :model="form" :rules="rules" ref="csRef" label-width="80px">
<el-row>
<el-col>
<el-form-item label="仪器" prop="yq">
<el-input v-model="form.yq" placeholder="请输入仪器" />
</el-form-item>
</el-col>
<el-col>
<el-form-item label="代号" prop="xxdh">
<el-input v-model="form.xxdh" placeholder="请输入代号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col>
<el-form-item label="类别" prop="xxlb">
<el-input v-model="form.xxlb" placeholder="请输入类别" />
</el-form-item>
</el-col>
<el-col>
<el-form-item label="参数值" prop="xxqz">
<el-input v-model="form.xxqz" placeholder="请输入参数值" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.bz" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, toRaw, computed, watch, nextTick } from 'vue'
import { paramList, addParam, updateParam, delParam, paramTree } from '@/api/system/parameter'
import { ElMessage, ElMessageBox } from 'element-plus';
const open = ref(false)
const title = ref("")
const queryParams = ref({
pageNum: 1,
pageSize: 10,
yq: undefined,
xxdh: undefined,
xxqz: undefined,
})
const form = ref({
yq: undefined,
xxdh: undefined,
xxqz: undefined,
xxlb: undefined,
bz: undefined
})
const tableData = ref([])
const loading = ref(true);
const showSearch = ref(true);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const deptName = ref("");
const rules = ref({
yq: [{ required: true, message: "仪器不能为空", trigger: "blur" },],
xxdh: [{ required: true, message: "代号不能为空", trigger: "blur" }],
xxlb: [{ required: true, message: "类别不能为空", trigger: "blur" },],
xxqz: [{ required: true, message: "值不能为空", trigger: ["blur", "change"] }],
})
const ids = ref([]);
const csRef = ref()
const deptTreeRef = ref()
const queryRef = ref()
/** 根据名称筛选部门树 */
watch(deptName, val => {
deptTreeRef.value.filter(val);
});
/** 通过条件过滤节点 */
const filterNode = (value: string, data: any) => {
if (!value) return true;
return data.label.indexOf(value) !== -1;
};
const deptOptions = ref([
{
label: '全部',
children: [
{
label: '仪器',
children: []
}
]
}
]);
/** 查询下拉树数据 */
const getDeptTree = () => {
paramTree().then((response: any) => {
const level1Nodes: any[] = [];
const level2Nodes: any[] = [];
response.data.forEach((item: any) => {
if (item.lx == 1) {
level1Nodes.push({ label: item.yq });
} else if (item.lx == 2) {
level2Nodes.push({ label: item.yq });
}
});
deptOptions.value = [
{
label: '全部',
children: [...level1Nodes, { label: '仪器', children: [...level2Nodes] }]
}
];
});
};
getDeptTree()
/** 节点单击事件 */
const handleNodeClick = (data: any) => {
if (data.label == '仪器') return
if (data.label == '全部') {
queryParams.value.yq = undefined;
handleQuery();
} else {
queryParams.value.yq = data.label;
handleQuery();
}
};
const handleAdd = () => {
reset();
open.value = true;
title.value = "添加参数";
nextTick(() => {
csRef.value?.resetFields();
});
};
const handleUpdate = (row: any) => {
reset();
open.value = true;
title.value = "修改参数";
nextTick(() => {
csRef.value?.resetFields();
form.value = Object.assign({}, row);
});
};
const handleImport = () => { };
// 保存
const submitForm = async () => {
const valid = await csRef.value.validate().catch(() => { });
if (!valid) return false;
if (title.value == "添加参数") {
addParam(form.value).then((res: any) => {
if (res.code == 0) {
ElMessage.success(res.msg);
open.value = false;
getList()
}
})
} else {
updateParam(form.value).then((res: any) => {
if (res.code == 0) {
ElMessage.success(res.msg);
open.value = false;
getList()
}
})
}
};
const cancel = () => {
open.value = false;
};
const handleSelectionChange = (selection: any) => {
ids.value = selection[0]
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 重置操作表单 */
function reset() {
form.value = {
yq: undefined,
xxdh: undefined,
xxlb: undefined,
xxqz: undefined,
bz: undefined,
};
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryRef.value?.resetFields();
getList()
}
/** 删除按钮操作 */
const handleDelete = (row: any) => {
ElMessageBox.confirm(
`确定删除数据吗?`, "提示",
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
delParam({ xxdh: row.xxdh, yq: row.yq }).then((res: any) => {
if (res.code == 0) {
getList()
ElMessage.success(res.msg);
}
})
})
.catch(() => {
})
};
/** 导出按钮操作 */
const handleExport = () => {
// proxy.download("system/user/export", {
// ...queryParams.value,
// }, `user_${new Date().getTime()}.xlsx`);
};
const getList = () => {
paramList(queryParams.value).then((res: any) => {
if (res.code == 200) {
loading.value = false
tableData.value = res.rows
total.value = res.total
}
})
}
getList()
</script>
<style lang="scss" scoped>
.el-form--inline .el-form-item {
margin-right: 12px;
}
.head-container {
height: 80vh;
overflow: auto;
}
</style>

38
tsconfig.json Normal file
View File

@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "esnext" /* 指定ECMAScript目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "esnext" /* 指定模块代码生成: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["esnext", "dom", "dom.iterable", "scripthost"] /* 指定要包含在编译中的库文件。 */,
"jsx": "preserve" /* 指定JSX代码生成:'preserve'、'react-native'或'react'。 */,
"isolatedModules": true /* 将每个文件作为单独的模块进行转译(类似于“ts.transpileModule”)。 */,
/* 严格的类型检查选项 */
"strict": true /* 启用所有严格的类型检查选项。 */,
/* 模块解析选项 */
"moduleResolution": "node" /* 指定模块解析策略:'node'(Node.js)或'classic'(TypeScript 1.6之前版本)。*/,
"baseUrl": "." /* 用于解析非绝对模块名称的基准目录。 */,
"paths": {
"@/*": ["src/*"]
} /* 一系列条目,这些条目将导入重新映射到相对于“baseUrl”的查找位置。*/,
"types": ["vite/client"] /* 要包含在编译中的类型声明文件。 */,
"allowSyntheticDefaultImports": true /*允许从没有默认导出的模块进行默认导入。这不会影响代码生成,只会影响类型检查。*/,
"esModuleInterop": true /* 通过为所有导入创建命名空间对象,实现CommonJS和ES模块之间的发射互操作性。这意味着“允许合成默认导入”。 */,
"experimentalDecorators": true /*启用对ES7装饰器的实验性支持。 */,
/* 高级选项 */
"skipLibCheck": true /* 跳过声明文件的类型检查. */,
"forceConsistentCasingInFileNames": true /*禁止对同一文件使用大小写不一致的引用。 */
},
"vueCompilerOptions": {
// 关键配置:关闭模板中的严格类型检查
"strictTemplates": false,
// // 可选:如果需要更精细的控制,可以单独关闭某些检查
// "templateOptions": {
// "allowUnknownInTemplate": true // 允许模板中使用未知属性
// }
},
"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"] ,// 指示不需要编译的文件目录
}

View File

@ -1,11 +1,19 @@
import { defineConfig, loadEnv } from 'vite'
import {
defineConfig,
loadEnv
} from 'vite'
import path from 'path'
import createVitePlugins from './vite/plugins'
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
export default defineConfig(({
mode,
command
}) => {
const env = loadEnv(mode, process.cwd())
const { VITE_APP_ENV } = env
const {
VITE_APP_ENV
} = env
return {
// 部署生产环境和开发环境下的URL。
// 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上
@ -18,21 +26,21 @@ export default defineConfig(({ mode, command }) => {
// 设置路径
'~': path.resolve(__dirname, './'),
// 设置别名
'@': path.resolve(__dirname, './src')
'@': path.resolve(__dirname, 'src')
},
// https://cn.vitejs.dev/config/#resolve-extensions
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
// vite 相关配置
server: {
port: 5173,
port: 8888,
host: true,
open: true,
open: false,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'http://localhost:9801',
// target: 'https://api.wzs.pub/mock/13',
target: 'http://47.97.125.165:8904',
// target: 'http://192.168.1.114:9801',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
},
@ -47,8 +55,7 @@ export default defineConfig(({ mode, command }) => {
//fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
css: {
postcss: {
plugins: [
{
plugins: [{
postcssPlugin: 'internal:charset-removal',
AtRule: {
charset: (atRule) => {
@ -57,8 +64,7 @@ export default defineConfig(({ mode, command }) => {
}
}
}
}
]
}]
}
}
}