前端最新版本同步

This commit is contained in:
Zhang.WH
2025-09-25 10:36:07 +08:00
parent a3a06d6f3c
commit 1276dc4adb
117 changed files with 11964 additions and 2466 deletions

View File

@@ -0,0 +1,59 @@
import request from '@/utils/request'
// 查询诊疗项目列表
export function getTreeList(queryParams) {
return request({
url: '/document/definition/treeList',
method: 'get',
params: queryParams
})
}
// 初始化
export function init() {
return request({
url: '/document/definition/init',
method: 'get'
})
}
// 新增
export function add(data) {
return request({
url: '/document/definition/add',
method: 'post',
data
})
}
// 修改
export function update(data) {
return request({
url: '/document/definition/update',
method: 'post',
data
})
}
// 查询就诊科室
export function getLocationTree(query) {
return request({
url: '/charge-manage/register/org-list',
method: 'get',
params: query
})
}
// 查询就诊科室
export function getDefinitionById(id) {
return request({
url: `/document/definition/${id}`,
method: 'get'
})
}
export function deleteDefinition(id) {
return request({
url: `/document/definition/delete/${id}`,
method: 'delete'
})
}

View File

@@ -0,0 +1,250 @@
<template>
<!-- 病历文件基本信息弹窗 -->
<el-dialog
:title="title"
v-model="dialogVisible"
width="900px"
destroy-on-close
@open="handleOpen"
>
<!-- 使用el-form包裹表单 -->
<el-form :model="formData" ref="formRef" :rules="rules" label-width="120px">
<el-form-item label="一级菜单" prop="primaryMenuEnum">
<el-select
v-model="formData.primaryMenuEnum"
placeholder="请选择一级菜单"
value-key="value"
>
<el-option
v-for="item in props.docTypes || []"
:key="item.value"
:label="item.info"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="二级菜单" prop="subMenu">
<el-input v-model="formData.subMenu" placeholder="二级菜单"></el-input>
</el-form-item>
<el-form-item label="版本" prop="version">
<el-input v-model="formData.version" placeholder="请输入版本"></el-input>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称"></el-input>
</el-form-item>
<el-form-item label="文件" prop="vueRouter">
<el-select v-model="formData.vueRouter" placeholder="请选择文件路径">
<el-option
v-for="item in components"
:key="item.name"
:label="item.name"
:value="item.name"
/>
</el-select>
<!-- <el-input v-model="formData.vueRouter" placeholder="请输入文件路径"></el-input>s -->
</el-form-item>
<el-form-item label="显示顺序" prop="displayOrder">
<el-input-number v-model="formData.displayOrder" :min="1" label="描述文字"></el-input-number>
</el-form-item>
<el-form-item label="使用范围" prop="useRangeEnum">
<div class="radio-group">
<el-radio
v-for="item in props.useRanges"
:key="item.value"
v-model="formData.useRangeEnum"
:label="item.value"
>{{item.info}}</el-radio>
{{ formData.organizationIds }}
</div>
</el-form-item>
<!-- 科室选择框仅当使用范围为科室使用时显示 -->
<el-form-item v-if="formData.useRangeEnum === 2" label="科室选择" prop="organizationIds">
<el-transfer
v-model="formData.organizationIds"
filterable
:titles="['未分配科室', '已分配科室']"
:button-texts="['移除', '添加']"
:format="{
noChecked: '${total}',
hasChecked: '${checked}/${total}',
}"
:props="{key: 'id', label: 'name',}"
:data="transferData"
>
</el-transfer>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer"></div>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</template>
<script setup >
import { ref, onMounted } from 'vue'
import useUserStore from '@/store/modules/user';
import { add, update,getLocationTree } from '../api';
import { ElMessage } from 'element-plus';
import { components } from '@/template';
const emits = defineEmits(['submitOk'])
const props = defineProps({
title: {
type: String,
default: '编辑病历文件信息'
},
formData: {
type: Object,
default: () => ({})
},
currentNodeData: {
type: Object,
default: () => ({})
},
docTypes: {
type: Array,
default: () => []
},
useRanges: {
type: Array,
default: () => []
},
})
const userStore = useUserStore();
const formRef = ref(null)
const dialogVisible= defineModel( 'dialogVisible', {
type: Boolean,
default: false
})
// 表单数据
const formData = ref({
primaryMenuEnum: undefined,
subMenu: '',
displayOrder: 1,
version: '',
name: '',
vueRouter: '',
useRangeEnum: 0, // 默认"暂不使用"0暂不使用1全院使用2科室使用
organizationIds: [], // 选中的科室ID列表整数类型
environment: '0'
});
// 表单验证规则(响应式,支持动态验证)
const rules = reactive({
primaryMenuEnum: [{ required: true, message: '请选择一级菜单', trigger: 'change' }],
// subMenu: [{ required: true, message: '请输入二级菜单', trigger: 'blur' }],
version: [{ required: true, message: '请输入版本', trigger: 'blur' }],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
vueRouter: [{ required: true, message: '请输入文件路径', trigger: 'blur' }],
displayOrder: [{ required: true, message: '请设置显示顺序', trigger: 'change' }],
useRangeEnum: [{ required: true, message: '请选择使用范围', trigger: 'change' }],
organizationIds: [
{
required: () => formData.value.useRangeEnum === 2, // 仅当"科室使用"时必填
message: '请选择科室',
trigger: ['change', 'blur']
}
]
});
const transferData = ref([]);
/** 查询科室树数据TreeSelect和Transfer共用 */
const getLocationInfo = () => {
getLocationTree().then((response) => {
transferData.value = response?.data || [];
}).catch((error) => {
ElMessage.error('获取科室树失败');
});
}
/** 递归将所有ID转换为整数类型 */
const convertIdsToNumbers = (item) => {
return {
...item,
id: Number(item.id), // 确保ID为整数
...(item.children && item.children.length > 0
? { children: item.children.map((child ) => convertIdsToNumbers(child)) }
: {})
};
};
const handleOpen = () => {
if (props.formData) {
console.log('props.formData', props.formData);
formData.value= props.formData
} else {
resetForm();
formRef.value.resetFields()
}
}
// 提交表单
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
// 表单验证通过,执行保存操作
saveForm();
} else {
// 表单验证失败
ElMessage.error('请填写必填项');
return false;
}
});
};
// 保存表单
const saveForm =async () => {
const userStore = useUserStore()
console.log('提交表单数据:', formData.value);
let data = { ...formData.value, tenantId: userStore.tenantId }
if (formData.value.useRangeEnum !== 2) {
formData.value.organizationIds = [];
}
try {
// 如果有当前节点数据,表示是编辑操作
if (props.currentNodeData) {
data.id = props.currentNodeData.id; // 添加ID
data.busNo = props.currentNodeData.busNo;
data.hospitalId = props.currentNodeData.hospitalId;
console.log('data',data)
const res = await update(data);
if (res.code == 200) {
ElMessage.success('更新成功');
emits('submitOk')
}else {
ElMessage.error('保存失败');
}
} else {
// 新建操作
const res = await add(data);
if (res.code == 200) {
ElMessage.success('保存成功');
emits('submitOk')
}else {
ElMessage.error('保存失败',error);
}
}
}
catch (error) {
console.log(error)
// ElMessage.error('保存失败',error);
}
}
// 重置表单
const resetForm = () => {
formRef.value?.resetFields();
formData.value.useRangeEnum = 0;
formData.value.environment = '0';
formData.value.organizationIds = [];
};
onMounted(()=>{
getLocationInfo()
})
</script>
<style lang="scss" scoped>
</style>

View File

@@ -1,2 +1,436 @@
<template>
</template>
<div class="case-templates-container">
<!-- 顶部工具栏 -->
<div class="toolbar">
<el-button type="primary" @click="newTemplate">新建</el-button>
<el-button type="primary" @click="editTemplate">编辑</el-button>
<el-button @click="refresh">刷新</el-button>
<el-button type="danger" @click="deleteTemplate">删除</el-button>
<el-button @click="printTemplate">打印</el-button>
</div>
<div class="content-area">
<!-- 左侧病历类型树 -->
<div class="left-panel">
<div style="margin-bottom: 10px">
<el-tree-select
v-model="orgId"
:data="orgOptions"
:props="{
value: 'id',
label: 'name',
children: 'children',
}"
value-key="id"
placeholder="请选择就诊科室"
check-strictly
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="locationTreeRef"
node-key="id"
highlight-current
default-expand-all
@node-click="initTemplateTree"
@clear="handleOrgClear"
clearable
/>
</div>
<div class="search-box">
<el-input
placeholder="病历名称搜索..."
v-model="searchKeyword"
></el-input>
<el-button class="search-btn" @click="handleSearch">查询</el-button>
</div>
<el-tree
ref="templateTree"
:data="templateData"
:props="defaultProps"
node-key="id"
@node-click="handleNodeClick"
class="template-tree"
></el-tree>
<el-button @click="toggleExpand">{{ isExpanded ? '全部收起' : '全部展开' }}</el-button>
</div>
<div class="middle-panel">
<el-tabs v-model="activeName" type="card" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="打印预览" name="first">
<!-- {{components}} -->
<component :is="currentComponent" />
</el-tab-pane>
<!-- <el-tab-pane label="编辑内容" name="second">
<component :is="currentComponent" />
</el-tab-pane> -->
</el-tabs>
</div>
</div>
<EditTemplate
v-model:dialogVisible="dialogVisible"
:title="currentNodeData ? '编辑病历文件信息' : '病历文件基本信息'"
:formData="formData"
:currentNodeData="currentNodeData"
@submitOk="handleSubmitOk"
:docTypes="templateDataInit.docTypes"
:useRanges="templateDataInit.useRanges"
/>
</div>
</template>
<script setup>
// 从Vue导入所需的API
import { ref, reactive, onMounted, defineAsyncComponent, nextTick, watch } from 'vue';
import { ElMessageBox, ElMessage, ElLoading, ElTree } from 'element-plus';
import { getTreeList, init, getDefinitionById, deleteDefinition,getLocationTree } from './api';
import EditTemplate from './components/editTemplate.vue';
// 添加当前模板路径和组件的响应式变量
const currentComponent = ref('');
const currentNodeData = ref(null); // 存储当前选中的节点数据
const isExpanded = ref(true); // 控制树形结构的展开状态
// 弹窗可见性
const dialogVisible = ref(false);
const orgId = ref('');
const orgOptions = ref([]); // 科室选项整数ID
const locationTreeRef = ref(null); // TreeSelect引用
const templateTree = ref(null);
const searchKeyword = ref(''); // 搜索关键字
const activeName = ref('first');
// Transfer组件选项类型定义ID为整数类型
// 表单数据
const formData = reactive({
primaryMenuEnum: undefined,
subMenu: '',
displayOrder: 1,
version: '',
name: '',
vueRouter: '',
useRangeEnum: 0, // 默认"暂不使用"0暂不使用1全院使用2科室使用
organizationIds: [], // 选中的科室ID列表整数类型
environment: '0',
});
const initFormData = () => {
formData.primaryMenuEnum = undefined;
formData.subMenu = '';
formData.displayOrder = 1;
formData.version = '';
formData.name = '';
formData.vueRouter = '';
formData.useRangeEnum = 0;
formData.organizationIds = [];
formData.environment = '0';
};
const getLocationInfo = () => {
getLocationTree().then((response) => {
orgOptions.value = response?.data || [];
}).catch((error) => {
ElMessage.error('获取科室树失败');
});
}
const handleSubmitOk = () => {
dialogVisible.value = false;
initFormData();
refresh();
};
// 组件初始化时加载基础数据
onMounted(() => {
getInit(); // 加载模板初始化数据(一级菜单、使用范围等)
getLocationInfo(); // 加载科室树
handleOrgClear(); // 初始化模板树(加载所有科室模板)
});
/** 将树形结构转换为Transfer组件所需的格式不扁平化保留层级关系 */
const convertTreeToTransferFormat = (tree) => {
return tree.map((item) => {
const option = {
key: item.id, // 整数ID
label: item.name,
disabled: false,
};
// 如果有子节点递归处理Transfer组件会自动处理层级显示
if (item.children && item.children.length > 0) {
option.children = convertTreeToTransferFormat(item.children);
}
return option;
});
};
// 病历模板树数据
const templateData = ref([]);
const templateDataInit = ref({}); // 初始化数据(菜单、使用范围)
// 树配置(模板树)
const defaultProps = {
children: 'children',
label: 'name',
value: 'id',
};
/** 过滤节点科室TreeSelect搜索 */
const filterNode = (value, data) => {
if (!value) return true;
return data?.name.toLowerCase().includes(value.toLowerCase()); // 不区分大小写搜索
};
/** 统一的API错误处理函数 */
const handleApiError = (userMessage, logMessage) => {
return (error) => {
// 记录详细错误日志,包括错误对象和调用栈
console.error(`${logMessage}:`, error);
// 显示用户友好的错误提示
ElMessage.error(
`${userMessage}失败${error.message ? ': ' + error.message : ',请刷新页面重试'}`
);
};
};
/** 加载模板初始化数据(一级菜单、使用范围枚举) */
const getInit = async () => {
try {
const response = await init();
templateDataInit.value = response.data || {};
console.log('模板初始化数据:', templateDataInit.value);
} catch (error) {
handleApiError('初始化', '初始化接口异常')(error);
}
};
/** 清除科室选择时,加载所有科室模板 */
const handleOrgClear = () => {
orgId.value = '';
initTemplateTree({ id: '' });
};
/** 搜索模板(可扩展按科室+关键字筛选) */
const handleSearch = () => {
console.log('搜索模板,关键字:', searchKeyword.value);
initTemplateTree({ id: orgId.value });
};
/** 初始化病历模板树(按科室筛选) */
function initTemplateTree(data) {
const queryParams = {
organizationId: data.id || '', // 科室ID空表示所有科室
name: searchKeyword.value || '', // 模板名称(空表示不筛选)
};
getTreeList(queryParams)
.then((res) => {
templateData.value = res.data || [];
nextTick().then(() => {
expandTree(); // 展开树节点
})
// console.log('模板树数据(按科室筛选):', templateData.value);
})
.catch((error) => {
handleApiError('获取模板列表', '获取模板树失败')(error);
templateData.value = [];
});
}
/** 编辑模板(打开弹窗并回显数据) */
const editTemplate = async () => {
if (!currentNodeData.value) {
ElMessage.warning('请先选择一个模板节点');
return;
}
const loading = ElLoading.service({
lock: true,
text: '加载模板信息...',
background: 'rgba(0, 0, 0, 0.7)',
});
try {
const response = await getDefinitionById(currentNodeData.value.id);
if (response.data) {
openEditDialog(response.data);
}
} catch (error) {
handleApiError('加载模板信息', '加载模板信息失败')(error);
} finally {
loading.close();
}
};
/** 打开编辑弹窗,回显选中的模板数据并正确初始化科室选择 */
const openEditDialog = async (nodeData) => {
currentNodeData.value = nodeData;
console.log('回显模板数据:', nodeData);
// 回显表单数据(与接口返回字段匹配)
formData.primaryMenuEnum = nodeData.primaryMenuEnum;
formData.subMenu = nodeData.subMenu;
formData.version = nodeData.version;
formData.name = nodeData.name;
formData.vueRouter = nodeData.vueRouter;
formData.displayOrder = nodeData.displayOrder;
formData.useRangeEnum = nodeData.useRangeEnum;
formData.environment = nodeData.environment || '0';
formData.organizationIds = nodeData.organizationIds.map((id) => id.toString());
// 打开弹窗
dialogVisible.value = true;
};
// 处理节点点击,根据后台返回的路径加载组件
const handleNodeClick = async (data, node) => {
console.log('点击节点:', data, node);
// 检查是否为子节点没有children或children为空
// const isLeafNode = !data.children || data.children.length === 0;
if (node.isLeaf) {
// 存储当前节点数据
currentNodeData.value = data.document;
// 检查是否为子节点没有children或children为空
currentComponent.value = data.document.vueRouter || '';
} else {
currentNodeData.value = null;
currentComponent.value = null;
}
};
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
console.log('展开状态:', templateTree.value.store);
expandTree();
};
const expandTree = () => {
templateTree.value.store._getAllNodes().forEach((node) => {
node.expanded = isExpanded.value;
});
};
const refresh = () => {
initTemplateTree({ id: orgId.value });
};
// 新建模板
const newTemplate = () => {
dialogVisible.value = true;
};
// 删除模板
const deleteTemplate = async () => {
// 1. 检查是否选中节点
if (!currentNodeData.value) {
ElMessage.warning('请先选择一个模板节点');
return;
}
let loading = null;
const templateName = currentNodeData.value.name;
const templateId = currentNodeData.value.id;
try {
// 2. 显示确认对话框,增加操作描述
const confirmResult = await ElMessageBox.confirm(
`确定要删除模板「${templateName}」吗?<br>此操作不可撤销,删除后将无法恢复。`,
'删除确认',
{
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true,
center: true,
closeOnClickModal: false,
}
);
// 3. 用户确认删除后执行操作
if (confirmResult === 'confirm') {
// 显示加载状态
loading = ElLoading.service({
lock: true,
text: '正在删除模板...',
background: 'rgba(0, 0, 0, 0.7)',
});
// 调用删除API
await deleteDefinition(templateId);
// 删除成功处理
ElMessage.success(`模板「${templateName}」删除成功`);
// 清空当前选中状态
currentNodeData.value = null;
initFormData();
currentComponent.value = null;
// 刷新列表
refresh();
}
} catch (error) {
// 错误处理区分用户取消和API错误
if (error === 'cancel' || error === undefined) {
// 用户取消删除,不显示错误提示
console.log('用户取消删除操作');
} else {
// API错误或其他错误使用统一的错误处理函数
handleApiError('删除模板', '删除模板失败')(error);
}
} finally {
// 确保加载状态总是被关闭
if (loading) {
loading.close();
}
}
};
// 打印模板
const printTemplate = () => {
window.print();
};
// 标签页点击事件
const handleClick = (tab) => {
console.log('标签页点击:', tab);
};
</script>
<style scoped>
.case-templates-container {
display: flex;
flex-direction: column;
height: 91vh;
}
.toolbar {
display: flex;
padding: 10px;
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
gap: 10px;
}
.content-area {
display: flex;
flex: 1;
overflow: hidden;
}
.left-panel {
width: 280px;
border-right: 1px solid #ddd;
display: flex;
flex-direction: column;
padding: 10px;
}
.search-box {
display: flex;
margin-bottom: 10px;
}
.search-btn {
margin-left: 5px;
}
.template-tree {
flex: 1;
overflow-y: auto;
}
.middle-panel {
flex: 1;
overflow-y: auto;
padding: 10px;
}
</style>