feat(basicmanage): 新增医嘱组套对话框组件和相关API

- 实现 MedicalOrderSetDialog.vue 组件,支持医嘱组套的新增、编辑功能
- 添加中药医嘱基础列表组件 tcmMedicineList.vue,支持键盘导航选择
- 创建医嘱组套相关API接口文件,包含个人、科室、全院组套的增删改查功能
- 实现医嘱组套的组合拆组功能,支持批量操作
- 集成分页、搜索、缓存等优化功能提升用户体验
- 添加表单验证和数据校验机制确保数据完整性
This commit is contained in:
2026-03-17 09:35:07 +08:00
parent 16f6fbb8cf
commit 03939fb232
4 changed files with 2103 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
import request from '@/utils/request'
/**
* 获取个人组套
* @param {*} queryParams
*/
export function getPersonalList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-personal',
method: 'get',
params: queryParams
})
}
/**
* 获取科室组套
* @param {*} queryParams
*/
export function getDeptList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-organization',
method: 'get',
params: queryParams
})
}
/**
* 获取全院组套
* @param {*} queryParams
*/
export function getAllList(queryParams) {
return request({
url: '/personalization/orders-group-package/get-hospital',
method: 'get',
params: queryParams
})
}
/**
* 保存个人组套
* @param {*} data
*/
export function savePersonal(data) {
return request({
url: '/personalization/orders-group-package/save-personal',
method: 'post',
data: data
})
}
/**
* 保存科室组套
* @param {*} data
*/
export function saveDepartment(data) {
return request({
url: '/personalization/orders-group-package/save-organization',
method: 'post',
data: data
})
}
/**
* 保存全院组套
* @param {*} data
*/
export function saveAll(data) {
return request({
url: '/personalization/orders-group-package/save-hospital',
method: 'post',
data: data
})
}
/**
* 查询组套明细
* @param {*} data
*/
export function queryGroupDetail(params) {
return request({
url: '/personalization/orders-group-package/get-group-package-detail',
method: 'get',
params: params
})
}
/**
* 删除组套
* @param {*} data
*/
export function deleteGroup(data) {
return request({
url: '/personalization/orders-group-package/group-package-detail?groupPackageId=' + data.groupPackageId,
method: 'delete'
})
}
/**
* 查询参与者下拉列表
* @param {*} data
*/
export function queryParticipantList(params) {
return request({
url: '/app-common/practitioner-list',
method: 'get',
params: params
})
}
/**
* 获取科室列表
*/
export function getOrgTree() {
return request({
url: '/base-data-manage/organization/organization',
method: 'get',
})
}
/**
* 获取中药列表
*/
export function getTcmMedicine(params) {
return request({
url: '/doctor-station/chinese-medical/tcm-advice-base-info',
method: 'get',
params: params,
});
}

View File

@@ -0,0 +1,269 @@
<template>
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="table-container">
<el-table
ref="adviceBaseRef"
height="350"
:data="adviceBaseList"
highlight-current-row
@current-change="handleCurrentChange"
row-key="adviceCode"
v-loading="loading"
@cell-click="clickRow"
>
<el-table-column label="名称" align="center" prop="adviceName" />
<el-table-column label="类型" align="center" prop="categoryCodeText" />
<el-table-column label="医保等级" align="center" prop="chrgitmLv_dictText" />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
<el-table-column label="库存数量" align="center">
<template #default="scope">{{ handleQuantity(scope.row) }}</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNum"
:page-size="20"
:total="total"
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue';
import { getTcmMedicine } from './api';
import { debounce } from 'lodash-es';
const props = defineProps({
searchKey: {
type: String,
default: '',
},
organizationId: {
type: String,
default: '',
},
});
const emit = defineEmits(['selectMedicine']);
const total = ref(0);
const loading = ref(false);
const adviceBaseRef = ref();
const tableWrapper = ref();
const currentIndex = ref(0);
const currentSelectRow = ref({});
const queryParams = ref({
pageSize: 20,
pageNum: 1,
searchKey: '',
organizationId: props.organizationId,
});
const adviceBaseList = ref([]);
// 前端缓存 - 使用 sessionStorage 持久化缓存
const TCM_CACHE_PREFIX = 'tcm_advice_cache_';
const TCM_CACHE_EXPIRE = 5 * 60 * 1000; // 缓存5分钟
function getTcmCacheKey(orgId, pageNum, searchKey) {
return TCM_CACHE_PREFIX + orgId + '_' + pageNum + '_' + (searchKey || 'none');
}
function getFromTcmCache(orgId, pageNum, searchKey) {
try {
const key = getTcmCacheKey(orgId, pageNum, searchKey);
const cachedData = sessionStorage.getItem(key);
if (!cachedData) return null;
const cacheData = JSON.parse(cachedData);
if (Date.now() - cacheData.timestamp > TCM_CACHE_EXPIRE) {
sessionStorage.removeItem(key);
return null;
}
console.log('从TCM前端缓存获取:', key);
return cacheData;
} catch (e) {
console.error('读取TCM缓存失败:', e);
return null;
}
}
function setToTcmCache(orgId, pageNum, searchKey, data, total) {
try {
const key = getTcmCacheKey(orgId, pageNum, searchKey);
const cacheData = {
data: data,
total: total,
timestamp: Date.now()
};
sessionStorage.setItem(key, JSON.stringify(cacheData));
console.log('写入TCM前端缓存:', key);
} catch (e) {
console.error('写入TCM缓存失败:', e);
}
}
// 防抖函数 - 避免重复请求
const debouncedGetList = debounce(
() => {
// 只有当 organizationId 有效时才请求
if (!queryParams.value.organizationId) {
console.log('organizationId 无效,跳过请求');
return;
}
getList();
},
300,
{ leading: false, trailing: true }
);
// 监听搜索关键字变化
watch(
() => props.searchKey,
(newValue) => {
queryParams.value.searchKey = newValue || '';
debouncedGetList();
},
{ deep: true }
);
// 监听 organizationId 变化
watch(
() => props.organizationId,
(newValue) => {
queryParams.value.organizationId = newValue;
// organizationId 变化时重新加载
if (newValue) {
debouncedGetList();
}
},
{ deep: true }
);
// 不再页面加载时立即请求,等待用户点击时再请求
function getList() {
const orgId = queryParams.value.organizationId;
const pageNum = queryParams.value.pageNum;
const searchKey = queryParams.value.searchKey;
// 尝试从本地缓存获取(只有第一页且无搜索关键字时使用缓存)
if (pageNum === 1 && !searchKey) {
const cached = getFromTcmCache(orgId, pageNum, searchKey);
if (cached) {
adviceBaseList.value = cached.data;
total.value = cached.total;
return;
}
}
// 显示 loading
loading.value = true;
getTcmMedicine(queryParams.value).then((res) => {
adviceBaseList.value = res.data.records || [];
total.value = res.data.total || 0;
// 缓存第一页数据
if (pageNum === 1 && !searchKey) {
setToTcmCache(orgId, pageNum, searchKey, adviceBaseList.value, total.value);
}
nextTick(() => {
currentIndex.value = 0;
if (adviceBaseList.value.length > 0 && adviceBaseRef.value) {
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
}
});
})
.finally(() => {
loading.value = false;
});
}
// 当前页改变
function handlePageChange(page) {
queryParams.value.pageNum = page;
getList();
}
// 处理键盘事件
const handleKeyDown = (event) => {
const key = event.key;
const data = adviceBaseList.value;
switch (key) {
case 'ArrowUp':
event.preventDefault();
if (currentIndex.value > 0) {
currentIndex.value--;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'ArrowDown':
event.preventDefault();
if (currentIndex.value < data.length - 1) {
currentIndex.value++;
setCurrentRow(data[currentIndex.value]);
}
break;
case 'Enter':
event.preventDefault();
if (currentSelectRow.value) {
emit('selectMedicine', currentSelectRow.value);
}
break;
}
};
function handleQuantity(row) {
if (row.inventoryList) {
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
}
return 0;
}
// 设置选中行(带滚动)
const setCurrentRow = (row) => {
adviceBaseRef.value.setCurrentRow(row);
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
if (tableBody && currentRowEl) {
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
};
// 当前行变化时更新索引
const handleCurrentChange = (currentRow) => {
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
currentSelectRow.value = currentRow;
};
function clickRow(row) {
emit('selectMedicine', row);
}
defineExpose({
handleKeyDown,
});
</script>
<style scoped>
.table-container {
display: flex;
flex-direction: column;
}
.pagination-wrapper {
display: flex;
justify-content: center;
padding: 10px 0;
background: #fff;
}
.popover-table-wrapper:focus {
outline: 2px solid #409eff;
}
</style>

View File

@@ -0,0 +1,820 @@
<template>
<div class="app-container tcm-medical-order-set">
<el-row :gutter="20" class="full-height">
<!-- 左侧树形结构 -->
<el-col :span="5" class="left-panel">
<el-card shadow="never" class="tree-card panel-shadow">
<template #header>
<span>中医组套</span>
</template>
<el-tree
:data="treeData"
node-key="id"
:props="treeProps"
default-expand-all
highlight-current
@node-click="(data, node) => handleTreeNodeClick(data, node)"
/>
</el-card>
</el-col>
<!-- 右侧操作区域 -->
<el-col :span="19" class="right-panel">
<!-- 上方表单:组套公共字段 -->
<el-card shadow="never" class="form-card panel-shadow">
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
<el-button type="primary" plain @click="clearForm">清空 </el-button>
<el-button type="primary" @click="createNew">新建 </el-button>
<el-button type="primary" @click="saveCommonForm">保存 </el-button>
</div>
<div class="common-bar">
<el-form :model="commonForm" :inline="true" class="demo-form-inline">
<el-form-item label="组套名称">
<el-input v-model="commonForm.name" placeholder="组套名称" clearable />
</el-form-item>
<el-form-item label="使用范围">
<el-select
v-model="commonForm.useRange"
placeholder="个人/科室/全院"
clearable
@change="handleRangeChange"
>
<el-option label="个人" value="personal" />
<el-option label="科室" value="department" />
<el-option label="全院" value="hospital" />
</el-select>
</el-form-item>
<el-form-item label="使用人" v-if="commonForm.useRange === 'personal'">
<el-select
v-model="commonForm.practitionerId"
placeholder="请选择使用人"
clearable
style="width: 200px"
>
<el-option
v-for="item in participantListOptions"
:key="item.practitionerId"
:label="item.practitionerName"
:value="item.practitionerId"
/>
</el-select>
</el-form-item>
<el-form-item label="科室" v-if="commonForm.useRange === 'department'">
<el-tree-select
clearable
v-model="commonForm.organizationId"
:data="organization"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="id"
check-strictly
default-expand-all
placeholder="请选择科室"
style="width: 200px"
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="用法">
<el-select v-model="commonForm.method" placeholder="用法" clearable>
<el-option
v-for="item in method_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-select v-model="commonForm.frequency" placeholder="频次" clearable>
<el-option
v-for="item in rate_code"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- 下方:中药明细卡片列表 -->
<el-card shadow="never" class="detail-card panel-shadow" style="height: 100%">
<div class="drug-card-list">
<!-- 已有药品卡片 -->
<el-card
v-for="(item, index) in drugList"
:key="item.id"
class="drug-card"
shadow="never"
>
<template v-if="!item.isEditing">
<div class="drug-card-body">
<div class="drug-top">
<div class="drug-view-name">{{ item.drugName || '-' }}</div>
</div>
<div class="drug-divider"></div>
<div class="drug-bottom" style="margin-top: 10px">
<div class="drug-bottom-left">
<span class="drug-view-qty">{{ item.quantity }} {{ item.unit }}</span>
</div>
<div class="drug-view-actions">
<el-button
type="primary"
text
:icon="Edit"
@click.stop="editDrug(item)"
title="编辑"
/>
<el-button
v-if="drugList.length > 1"
type="danger"
text
:icon="Delete"
@click.stop="removeDrug(index)"
title="删除"
/>
</div>
</div>
</div>
</template>
<template v-else>
<div class="drug-card-body">
<div class="drug-top">
<el-form :model="item" class="drug-card-form">
<el-form-item>
<el-popover
:popper-style="{ padding: '0' }"
placement="bottom-start"
:visible="item.showPopover"
:width="800"
>
<tcmMedicineList
:ref="(el) => setMedicineTableRef(el, item)"
:searchKey="item.searchKey"
:organizationId="commonForm.organizationId"
@selectMedicine="(row) => handleSelectMedicine(row, item)"
/>
<template #reference>
<el-input
v-model="item.drugName"
placeholder="请选择中药"
clearable
@input="(val) => handleSearchKeyChange(val, item)"
@click="() => handleClick(item)"
@keydown="(e) => handleKeyDown(e, item)"
@blur="() => handleBlur(item)"
/>
</template>
</el-popover>
</el-form-item>
</el-form>
</div>
<div class="drug-bottom">
<div class="drug-bottom-left">
<div class="quantity-wrap">
<el-input v-model="item.quantity" />
<el-input value="g" style="width: 80px" disabled />
</div>
</div>
</div>
<div class="drug-edit-actions">
<el-button type="primary" link @click.stop="saveDrug(item)"> 保存 </el-button>
<el-button type="danger" link @click.stop="removeDrug(index)"> 删除 </el-button>
</div>
</div>
</template>
</el-card>
<!-- 始终存在的新增卡片 -->
<el-card class="drug-card add-card" shadow="never" @click="addDrug">
<div class="add-card-content">
<el-icon class="add-icon">
<Plus />
</el-icon>
<span>新增</span>
</div>
</el-card>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup name="TcmMedicalOrderSet">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { ElMessage } from 'element-plus';
import {
getPersonalList,
getDeptList,
getAllList,
queryGroupDetail,
queryParticipantList,
getOrgTree,
savePersonal,
saveDepartment,
saveAll,
} from './components/api';
import TcmMedicineList from './components/tcmMedicineList.vue';
import { Edit, Delete, Plus } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance();
// 字典数据
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
// 左侧树形结构数据
const treeData = ref([]);
const treeProps = {
children: 'children',
label: 'label',
value: 'value',
};
// 加载左侧树数据
function loadTreeData() {
Promise.all([
getPersonalList({ tcmFlag: 1 }),
getDeptList({ tcmFlag: 1 }),
getAllList({ tcmFlag: 1 }),
])
.then(([personalRes, deptRes, allRes]) => {
const personalList = personalRes?.data || [];
const deptList = deptRes?.data || [];
const allList = allRes?.data || [];
treeData.value = [
{
id: 'personal',
label: '个人',
children: personalList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: item.practitionerId || '',
organizationId: '',
})),
},
{
id: 'department',
label: '科室',
children: deptList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: '',
organizationId: item.organizationId || '',
})),
},
{
id: 'hospital',
label: '全院',
children: allList.map((item) => ({
id: item.groupPackageId,
value: item.groupPackageId,
label: item.name,
practitionerId: '',
organizationId: '',
})),
},
];
})
.catch(() => {
ElMessage.error('加载组套树失败');
});
}
onMounted(() => {
loadTreeData();
loadParticipantList();
loadOrganization();
});
// 使用人列表
const participantListOptions = ref([]);
// 科室树形数据
const organization = ref([]);
// 加载使用人列表
function loadParticipantList() {
queryParticipantList().then((res) => {
participantListOptions.value = res.data || [];
});
}
// 加载科室树
function loadOrganization() {
getOrgTree().then((res) => {
organization.value = res.data.records || [];
});
}
// 使用范围变更
function handleRangeChange() {
commonForm.practitionerId = '';
commonForm.organizationId = '';
}
// 上方公共字段表单
const commonForm = reactive({
name: '',
useRange: '',
practitionerId: '',
organizationId: '',
method: '',
frequency: '',
});
// 下方药品卡片列表
const drugList = ref([]);
const nextDrugId = ref(1);
const currentSetName = ref('');
// 动态设置表格组件引用
function setMedicineTableRef(el, item) {
if (el) {
item.tableRef = el;
}
}
function createEmptyDrug() {
return {
id: nextDrugId.value++,
drugCode: '',
drugName: '',
orderDefinitionId: '',
quantity: 1,
unitCode: '',
unit: 'g',
isEditing: true,
showPopover: false,
searchKey: '',
tableRef: null,
rateCode: '',
methodCode: '',
};
}
// 点击树节点
function handleTreeNodeClick(data, node) {
if (data.children != undefined) {
return;
}
// 从父节点获取使用范围personal/department/hospital
const useRange = node.parent?.data?.id || '';
// 组套名称从树节点获取
commonForm.name = data.label || '';
commonForm.useRange = useRange;
// 从树节点数据中获取使用人/科室
commonForm.practitionerId = data.practitionerId || '';
commonForm.organizationId = data.organizationId || '';
// 调用查询接口回显数据
queryGroupDetail({ groupPackageId: data.value })
.then((res) => {
const detailList = res.data || [];
if (detailList.length === 0) {
drugList.value = [createEmptyDrug()];
return;
}
// 取第一个药品的频次和用法赋值到表单
const firstDrug = detailList[0];
commonForm.method = firstDrug.methodCode || '';
commonForm.frequency = firstDrug.rateCode || '';
// 回显药品列表
drugList.value = detailList.map((detail, index) => ({
id: index,
drugCode: '',
drugName: detail.orderDefinitionName || '',
orderDefinitionId: detail.orderDefinitionId,
quantity: detail.quantity,
unitCode: detail.unitCode,
unit: detail.unitCode_dictText || 'g',
isEditing: false,
showPopover: false,
searchKey: '',
tableRef: null,
rateCode: detail.rateCode,
methodCode: detail.methodCode,
}));
currentSetName.value = data.label;
})
.catch(() => {
ElMessage.error('加载组套详情失败');
});
}
// 新增药品
function addDrug() {
// 如果有药品列表,检查上一个药品是否未保存
if (drugList.value.length > 0) {
const last = drugList.value[drugList.value.length - 1];
// 如果上一个药品未选择药品,不允许添加下一个
if (!last.orderDefinitionId) {
ElMessage.warning('请先选择上一个药品');
return;
}
// 如果上一个药品已有药品ID且处于编辑状态自动保存
if (last.isEditing) {
const msg = validateDrug(last);
if (!msg) {
last.isEditing = false;
}
}
}
drugList.value.push(createEmptyDrug());
}
// 删除药品
function removeDrug(index) {
if (drugList.value.length <= 1) {
ElMessage.warning('至少保留一条药品信息');
return;
}
drugList.value.splice(index, 1);
}
function validateDrug(item) {
if (!item.orderDefinitionId) return '请选择中药';
if (!item.quantity || Number(item.quantity) <= 0) return '请填写数量';
return '';
}
function saveDrug(item) {
const msg = validateDrug(item);
if (msg) {
ElMessage.warning(msg);
return false;
}
item.isEditing = false;
return true;
}
function editDrug(item) {
item.isEditing = true;
}
// 处理搜索关键字变化
function handleSearchKeyChange(value, item) {
item.searchKey = value;
}
// 处理点击输入框
function handleClick(item) {
item.showPopover = true;
}
// 处理失焦
function handleBlur(item) {
item.showPopover = false;
item.searchKey = '';
}
// 处理键盘事件(上下键和回车)
function handleKeyDown(event, item) {
if (!item.showPopover) return;
const key = event.key;
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(key)) {
event.preventDefault();
if (item.tableRef) {
item.tableRef.handleKeyDown(event);
}
}
}
// 选择中药时,回填信息
function handleSelectMedicine(row, item) {
item.drugCode = row.adviceCode;
item.drugName = row.adviceName;
item.orderDefinitionId = row.adviceDefinitionId;
item.unitCode = row.unitCode;
item.unit = row.unitCode_dictText || 'g';
item.showPopover = false;
item.searchKey = '';
}
// 清空表单
function clearForm() {
commonForm.name = '';
commonForm.useRange = '';
commonForm.practitionerId = '';
commonForm.organizationId = '';
commonForm.method = '';
commonForm.frequency = '';
drugList.value = [];
}
// 新建组套
function createNew() {
clearForm();
commonForm.useRange = 'personal';
drugList.value = [createEmptyDrug()];
}
// 保存组套
function saveCommonForm() {
// 校验组套名称
if (!commonForm.name) {
ElMessage.warning('请填写组套名称');
return;
}
// 校验使用范围
if (!commonForm.useRange) {
ElMessage.warning('请选择使用范围');
return;
}
// 校验个人时选择使用人
if (commonForm.useRange === 'personal' && !commonForm.practitionerId) {
ElMessage.warning('请选择使用人');
return;
}
// 校验科室时选择科室
if (commonForm.useRange === 'department' && !commonForm.organizationId) {
ElMessage.warning('请选择科室');
return;
}
// 校验药品列表
if (drugList.value.length === 0) {
ElMessage.warning('请添加药品');
return;
}
// 校验每个药品
for (let i = 0; i < drugList.value.length; i++) {
const item = drugList.value[i];
if (!item.orderDefinitionId) {
ElMessage.warning(`请选择第${i + 1}个药品`);
return;
}
if (!item.quantity || Number(item.quantity) <= 0) {
ElMessage.warning(`请填写第${i + 1}个药品的数量`);
return;
}
}
// 生成组套ID时间戳
const groupId = Date.now();
// 构建详情列表
const detailList = drugList.value.map((item) => ({
orderDefinitionId: item.orderDefinitionId,
orderDefinitionTable: 'order_definition',
quantity: item.quantity,
unitCode: item.unitCode,
dose: item.quantity,
rateCode: commonForm.frequency,
dispensePerDuration: 7,
methodCode: commonForm.method,
doseQuantity: item.quantity,
groupId: groupId,
}));
// 构建请求数据
const data = {
name: commonForm.name,
tcmFlag: 1,
detailList: detailList,
};
// 根据使用范围添加不同字段
if (commonForm.useRange === 'personal') {
data.practitionerId = commonForm.practitionerId;
} else if (commonForm.useRange === 'department') {
data.organizationId = commonForm.organizationId;
}
// 全院不传 practitionerId 和 organizationId
// 调用对应接口
let saveApi;
if (commonForm.useRange === 'personal') {
saveApi = savePersonal;
} else if (commonForm.useRange === 'department') {
saveApi = saveDepartment;
} else {
saveApi = saveAll;
}
saveApi(data)
.then(() => {
ElMessage.success('保存成功');
loadTreeData();
})
.catch((err) => {
ElMessage.error('保存失败:' + (err.message || '未知错误'));
});
}
</script>
<style scoped>
.tcm-medical-order-set {
height: 90vh;
}
.full-height {
height: 100%;
}
.left-panel,
.right-panel {
height: 100%;
}
.tree-card {
height: 100%;
}
.right-panel {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-card,
.detail-card {
width: 100%;
}
.panel-shadow {
border: 1px solid var(--el-border-color);
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.1);
}
.common-form {
display: flex;
flex-wrap: wrap;
gap: 10px 14px;
}
.common-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
.common-item {
width: 300px;
margin-bottom: 0;
}
.common-item :deep(.el-input__wrapper),
.common-item :deep(.el-select__wrapper) {
height: 40px;
}
.common-item :deep(.el-input__inner) {
height: 40px;
}
.drug-card-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.drug-card {
width: 220px;
border: 1px solid var(--el-border-color);
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.12);
height: 120px;
}
.drug-card :deep(.el-card__body) {
padding: 10px 10px 8px;
}
.drug-card-body {
display: flex;
flex-direction: column;
height: 120px;
}
.drug-top {
flex: 0 0 auto;
}
.drug-divider {
margin: 6px 0;
border-top: 1px dashed var(--el-border-color);
}
.drug-bottom {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.drug-bottom-left {
flex: 1 1 auto;
min-width: 0;
}
.drug-card-form {
margin-top: 0;
}
.compact-item {
margin-bottom: 8px;
}
.compact-item :deep(.el-form-item__label) {
padding: 0 0 4px;
line-height: 1.2;
}
.compact-item :deep(.el-form-item__content) {
line-height: 1.2;
}
.quantity-wrap {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
}
.unit-select {
width: 80px;
}
.drug-select {
flex: 1;
min-width: 0;
}
.drug-edit-actions {
display: flex;
justify-content: flex-end;
gap: 0px;
padding-top: 2px;
flex-shrink: 0;
}
.drug-view-name {
font-weight: 600;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.drug-view-qty {
font-size: 13px;
color: var(--el-text-color-regular);
flex: none;
}
.drug-view-actions {
display: flex;
justify-content: flex-end;
gap: 6px;
flex-shrink: 0;
}
.drug-view-actions :deep(.el-button),
.drug-edit-actions :deep(.el-button) {
padding: 4px;
min-width: 0;
}
.add-card {
border-style: dashed;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-color-primary);
box-shadow: 0 3px 14px rgba(64, 158, 255, 0.18);
}
.add-card-content {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.add-icon {
font-size: 18px;
}
.empty-tip {
margin-top: 80px;
}
.demo-form-inline .el-input {
--el-input-width: 200px;
}
.demo-form-inline .el-select {
--el-select-width: 200px;
}
.el-form-item--default {
margin-bottom: 10px;
}
</style>