fix: 常用方、患者管理
This commit is contained in:
@@ -0,0 +1,498 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 药品搜索选择组件(自定义实现)
|
||||||
|
*
|
||||||
|
* @description 自定义药品搜索下拉选择器,不依赖antd的Select组件
|
||||||
|
* - 支持中药/西药类型切换
|
||||||
|
* - 下拉选项展示:药品图片、名称、供应商、规格、价格
|
||||||
|
* - 选择时返回完整药品数据(包含默认用法字段)
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { ref, watch, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
|
import { SearchOutlined, LoadingOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { Input, Spin } from 'ant-design-vue';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
|
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||||
|
|
||||||
|
// ==================== Props 定义 ====================
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/**
|
||||||
|
* 药品类型
|
||||||
|
* 1-中药,2-西药
|
||||||
|
*/
|
||||||
|
type?: number;
|
||||||
|
/**
|
||||||
|
* 占位提示文本
|
||||||
|
*/
|
||||||
|
placeholder?: string;
|
||||||
|
/**
|
||||||
|
* 诊所ID
|
||||||
|
*/
|
||||||
|
storeId?: number;
|
||||||
|
/**
|
||||||
|
* 是否禁用
|
||||||
|
*/
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
type: 1,
|
||||||
|
placeholder: '输入药品名称搜索',
|
||||||
|
storeId: 2,
|
||||||
|
disabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== Emits 定义 ====================
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
/**
|
||||||
|
* 选中药品时触发
|
||||||
|
* @param drug 完整的药品数据
|
||||||
|
*/
|
||||||
|
(e: 'select', drug: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// ==================== 响应式数据 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索关键词
|
||||||
|
*/
|
||||||
|
const searchKeyword = ref('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否正在搜索
|
||||||
|
*/
|
||||||
|
const isSearching = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索结果列表
|
||||||
|
*/
|
||||||
|
const searchResults = ref<any[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否显示下拉列表
|
||||||
|
*/
|
||||||
|
const showDropdown = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件容器引用
|
||||||
|
*/
|
||||||
|
const containerRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前高亮的选项索引(用于键盘导航)
|
||||||
|
*/
|
||||||
|
const highlightIndex = ref(-1);
|
||||||
|
|
||||||
|
// ==================== 方法定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索药品
|
||||||
|
* @param keyword 搜索关键词
|
||||||
|
* @description 调用后端API搜索药品,支持按名称和拼音搜索
|
||||||
|
*/
|
||||||
|
const searchDrugs = debounce(async (keyword: string) => {
|
||||||
|
if (!keyword || keyword.length < 1) {
|
||||||
|
searchResults.value = [];
|
||||||
|
showDropdown.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching.value = true;
|
||||||
|
showDropdown.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getProductListDoctorReception({
|
||||||
|
name: keyword,
|
||||||
|
type: props.type,
|
||||||
|
store_id: props.storeId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理返回数据
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
searchResults.value = res.map((item: any) => ({
|
||||||
|
// 保留原始数据
|
||||||
|
...item,
|
||||||
|
// 提取常用字段便于访问
|
||||||
|
_id: item.id,
|
||||||
|
_drugId: item.drug_id || item.drug?.id,
|
||||||
|
_drugName: item.drug?.drug_name || item.drug_name || '',
|
||||||
|
_image: item.drug?.image || '',
|
||||||
|
_specification: item.drug?.specification || '',
|
||||||
|
_supplier: item.drug?.supplier?.name || '',
|
||||||
|
_price: item.price || 0,
|
||||||
|
// 默认用法字段
|
||||||
|
_timeId: item.drug?.time_id || 0,
|
||||||
|
_typeId: item.drug?.type_id || 0,
|
||||||
|
_frequencyId: item.drug?.frequency_id || 0,
|
||||||
|
_unitId: item.drug?.unit_id || 0,
|
||||||
|
_number: item.drug?.number || 1,
|
||||||
|
// 用法名称
|
||||||
|
_useNum: item.drug?.useNum,
|
||||||
|
_useType: item.drug?.useType,
|
||||||
|
_useFrequency: item.drug?.useFrequency,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
searchResults.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('搜索药品失败:', error);
|
||||||
|
searchResults.value = [];
|
||||||
|
} finally {
|
||||||
|
isSearching.value = false;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理输入变化
|
||||||
|
* @param e 输入事件
|
||||||
|
*/
|
||||||
|
function handleInput(e: Event) {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
searchKeyword.value = target.value;
|
||||||
|
highlightIndex.value = -1;
|
||||||
|
searchDrugs(target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理输入框获得焦点
|
||||||
|
*/
|
||||||
|
function handleFocus() {
|
||||||
|
if (searchResults.value.length > 0) {
|
||||||
|
showDropdown.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理选中药品
|
||||||
|
* @param drug 选中的药品数据
|
||||||
|
*/
|
||||||
|
function handleSelectDrug(drug: any) {
|
||||||
|
emit('select', drug);
|
||||||
|
|
||||||
|
// 清空搜索状态
|
||||||
|
searchKeyword.value = '';
|
||||||
|
searchResults.value = [];
|
||||||
|
showDropdown.value = false;
|
||||||
|
highlightIndex.value = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理键盘事件
|
||||||
|
* @param e 键盘事件
|
||||||
|
*/
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
if (!showDropdown.value || searchResults.value.length === 0) return;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault();
|
||||||
|
highlightIndex.value = Math.min(
|
||||||
|
highlightIndex.value + 1,
|
||||||
|
searchResults.value.length - 1,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault();
|
||||||
|
highlightIndex.value = Math.max(highlightIndex.value - 1, 0);
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
e.preventDefault();
|
||||||
|
if (highlightIndex.value >= 0) {
|
||||||
|
handleSelectDrug(searchResults.value[highlightIndex.value]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
showDropdown.value = false;
|
||||||
|
highlightIndex.value = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理点击外部关闭下拉
|
||||||
|
* @param e 点击事件
|
||||||
|
*/
|
||||||
|
function handleClickOutside(e: MouseEvent) {
|
||||||
|
if (containerRef.value && !containerRef.value.contains(e.target as Node)) {
|
||||||
|
showDropdown.value = false;
|
||||||
|
highlightIndex.value = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 生命周期 ====================
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', handleClickOutside);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', handleClickOutside);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 监听 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听类型变化,清空搜索结果
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.type,
|
||||||
|
() => {
|
||||||
|
searchResults.value = [];
|
||||||
|
showDropdown.value = false;
|
||||||
|
searchKeyword.value = '';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="drug-search-select">
|
||||||
|
<!-- 搜索输入框 -->
|
||||||
|
<Input
|
||||||
|
v-model:value="searchKeyword"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
allow-clear
|
||||||
|
@input="handleInput"
|
||||||
|
@focus="handleFocus"
|
||||||
|
@keydown="handleKeydown"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<LoadingOutlined v-if="isSearching" class="text-gray-400" />
|
||||||
|
<SearchOutlined v-else class="text-gray-400" />
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
|
||||||
|
<!-- 下拉列表 -->
|
||||||
|
<div v-if="showDropdown" class="drug-dropdown">
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<div v-if="isSearching" class="drug-dropdown__loading">
|
||||||
|
<Spin size="small" />
|
||||||
|
<span>搜索中...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无结果 -->
|
||||||
|
<div
|
||||||
|
v-else-if="searchResults.length === 0"
|
||||||
|
class="drug-dropdown__empty"
|
||||||
|
>
|
||||||
|
暂无匹配药品
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 结果列表 -->
|
||||||
|
<div v-else class="drug-dropdown__list">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in searchResults"
|
||||||
|
:key="item._id"
|
||||||
|
class="drug-item"
|
||||||
|
:class="{ 'drug-item--active': index === highlightIndex }"
|
||||||
|
@click="handleSelectDrug(item)"
|
||||||
|
@mouseenter="highlightIndex = index"
|
||||||
|
>
|
||||||
|
<!-- 药品图片 -->
|
||||||
|
<div class="drug-item__image">
|
||||||
|
<img
|
||||||
|
v-if="item._image"
|
||||||
|
:src="item._image"
|
||||||
|
alt=""
|
||||||
|
class="drug-item__img"
|
||||||
|
@error="(e) => (e.target as HTMLImageElement).style.display = 'none'"
|
||||||
|
/>
|
||||||
|
<div v-else class="drug-item__img-placeholder">无图</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 药品信息 -->
|
||||||
|
<div class="drug-item__info">
|
||||||
|
<!-- 药品名称 -->
|
||||||
|
<div class="drug-item__name">{{ item._drugName }}</div>
|
||||||
|
<!-- 规格和供应商 -->
|
||||||
|
<div class="drug-item__meta">
|
||||||
|
<span v-if="item._specification" class="drug-item__spec">
|
||||||
|
规格:{{ item._specification }}
|
||||||
|
</span>
|
||||||
|
<span v-if="item._supplier" class="drug-item__supplier">
|
||||||
|
供应商:{{ item._supplier }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 价格 -->
|
||||||
|
<div class="drug-item__price">
|
||||||
|
¥{{ Number(item._price).toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.drug-search-select {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1050;
|
||||||
|
margin-top: 4px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
max-height: 400px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__loading,
|
||||||
|
&__empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: #d9d9d9;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&--active {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__img {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__img-placeholder {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spec {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__supplier {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__price {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗色模式适配 */
|
||||||
|
.dark {
|
||||||
|
.drug-dropdown {
|
||||||
|
background: #1f2937;
|
||||||
|
border-color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-item {
|
||||||
|
&:hover,
|
||||||
|
&--active {
|
||||||
|
background-color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__img-placeholder {
|
||||||
|
background: #374151;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spec {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__supplier {
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
apps/web-antd/src/components/drug-search-select/index.ts
Normal file
9
apps/web-antd/src/components/drug-search-select/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* 药品搜索选择组件
|
||||||
|
*
|
||||||
|
* @description 封装药品搜索下拉选择器,展示药品详细信息
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
export { default as DrugSearchSelect } from './drug-search-select.vue';
|
||||||
|
|
||||||
@@ -85,9 +85,11 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
const selectedStoreId = ref<number | null>(null); // 用户选择的诊所ID
|
const selectedStoreId = ref<number | null>(null); // 用户选择的诊所ID
|
||||||
const sendMode = ref(0); // 0=默认(使用医生当前诊所),1=自定义(使用挂号诊所)
|
const sendMode = ref(0); // 0=默认(使用医生当前诊所),1=自定义(使用挂号诊所)
|
||||||
|
|
||||||
// 获取localStorage key(修改为在线复诊前缀)
|
// 获取localStorage key(按类型分开存储)
|
||||||
const getStorageKey = () =>
|
const getStorageKey = (category?: number) => {
|
||||||
`onlineConsultation-prescriptionData${currentRegisterId.value}`;
|
const cat = category ?? activeCategory.value;
|
||||||
|
return `onlineConsultation-prescriptionData_${cat}_${currentRegisterId.value}`;
|
||||||
|
};
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const totalProductCost = computed(() => {
|
const totalProductCost = computed(() => {
|
||||||
@@ -201,6 +203,27 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
// 设置当前注册ID
|
// 设置当前注册ID
|
||||||
currentRegisterId.value = registerId;
|
currentRegisterId.value = registerId;
|
||||||
|
|
||||||
|
// 恢复之前保存的 activeCategory
|
||||||
|
const savedCategory = localStorage.getItem(
|
||||||
|
`onlineConsultation-activeCategory${registerId}`
|
||||||
|
);
|
||||||
|
if (savedCategory) {
|
||||||
|
activeCategory.value = Number.parseInt(savedCategory);
|
||||||
|
} else {
|
||||||
|
// 检查哪个存储键有数据
|
||||||
|
const chineseData = localStorage.getItem(
|
||||||
|
`onlineConsultation-prescriptionData_1_${registerId}`
|
||||||
|
);
|
||||||
|
const westData = localStorage.getItem(
|
||||||
|
`onlineConsultation-prescriptionData_2_${registerId}`
|
||||||
|
);
|
||||||
|
if (chineseData && JSON.parse(chineseData).length > 0) {
|
||||||
|
activeCategory.value = 1;
|
||||||
|
} else if (westData && JSON.parse(westData).length > 0) {
|
||||||
|
activeCategory.value = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载localStorage数据
|
// 加载localStorage数据
|
||||||
loadFromLocalStorage();
|
loadFromLocalStorage();
|
||||||
|
|
||||||
@@ -228,6 +251,27 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
// 设置当前注册ID
|
// 设置当前注册ID
|
||||||
currentRegisterId.value = registerId;
|
currentRegisterId.value = registerId;
|
||||||
|
|
||||||
|
// 恢复之前保存的 activeCategory
|
||||||
|
const savedCategory = localStorage.getItem(
|
||||||
|
`onlineConsultation-activeCategory${registerId}`
|
||||||
|
);
|
||||||
|
if (savedCategory) {
|
||||||
|
activeCategory.value = Number.parseInt(savedCategory);
|
||||||
|
} else {
|
||||||
|
// 检查哪个存储键有数据
|
||||||
|
const chineseData = localStorage.getItem(
|
||||||
|
`onlineConsultation-prescriptionData_1_${registerId}`
|
||||||
|
);
|
||||||
|
const westData = localStorage.getItem(
|
||||||
|
`onlineConsultation-prescriptionData_2_${registerId}`
|
||||||
|
);
|
||||||
|
if (chineseData && JSON.parse(chineseData).length > 0) {
|
||||||
|
activeCategory.value = 1;
|
||||||
|
} else if (westData && JSON.parse(westData).length > 0) {
|
||||||
|
activeCategory.value = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载localStorage数据
|
// 加载localStorage数据
|
||||||
loadFromLocalStorage();
|
loadFromLocalStorage();
|
||||||
|
|
||||||
@@ -458,6 +502,33 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 切换药品用法编辑状态
|
||||||
|
const toggleEditDrug = (index: number) => {
|
||||||
|
const newDrugs = [...currentDrugs.value];
|
||||||
|
newDrugs[index].isEditing = !newDrugs[index].isEditing;
|
||||||
|
updateCurrentDrugs(newDrugs);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存药品用法编辑
|
||||||
|
const saveEditDrug = (index: number) => {
|
||||||
|
const newDrugs = [...currentDrugs.value];
|
||||||
|
const drug = newDrugs[index];
|
||||||
|
// 更新 ID 字段
|
||||||
|
drug.type_id = drug.use_type?.id;
|
||||||
|
drug.frequency_id = drug.use_frequency?.id;
|
||||||
|
drug.time_id = drug.use_num?.id;
|
||||||
|
drug.unit_id = drug.unit?.id;
|
||||||
|
drug.isEditing = false;
|
||||||
|
updateCurrentDrugs(newDrugs);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新药品用法字段
|
||||||
|
const updateDrugUsage = (index: number, field: string, value: any) => {
|
||||||
|
const newDrugs = [...currentDrugs.value];
|
||||||
|
newDrugs[index][field] = value;
|
||||||
|
updateCurrentDrugs(newDrugs);
|
||||||
|
};
|
||||||
|
|
||||||
// 新药品操作
|
// 新药品操作
|
||||||
const selectNewDrugInfo = () => {
|
const selectNewDrugInfo = () => {
|
||||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
||||||
@@ -791,12 +862,26 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const changeCategory = (categoryValue: number) => {
|
const changeCategory = (categoryValue: number) => {
|
||||||
activeCategory.value = categoryValue;
|
// 先保存当前类型的药品数据
|
||||||
updateCurrentDrugs([]);
|
localStorage.setItem(
|
||||||
|
getStorageKey(activeCategory.value),
|
||||||
|
JSON.stringify(currentDrugs.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 保存 Tab 类型
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
`onlineConsultation-activeCategory${currentRegisterId.value}`,
|
`onlineConsultation-activeCategory${currentRegisterId.value}`,
|
||||||
categoryValue.toString(),
|
categoryValue.toString(),
|
||||||
);
|
);
|
||||||
|
activeCategory.value = categoryValue;
|
||||||
|
|
||||||
|
// 加载新类型对应的药品数据
|
||||||
|
const stored = localStorage.getItem(getStorageKey(categoryValue));
|
||||||
|
if (stored) {
|
||||||
|
currentDrugs.value.splice(0, currentDrugs.value.length, ...JSON.parse(stored));
|
||||||
|
} else {
|
||||||
|
currentDrugs.value.splice(0, currentDrugs.value.length);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
@@ -875,6 +960,9 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
updateDrugQuantity,
|
updateDrugQuantity,
|
||||||
increment,
|
increment,
|
||||||
decrement,
|
decrement,
|
||||||
|
toggleEditDrug,
|
||||||
|
saveEditDrug,
|
||||||
|
updateDrugUsage,
|
||||||
selectNewDrugInfo,
|
selectNewDrugInfo,
|
||||||
selectOldDrugInfo,
|
selectOldDrugInfo,
|
||||||
setSelectChineseIndex,
|
setSelectChineseIndex,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
const prefix = 'chat-friends/';
|
const prefix = 'chat-friends/';
|
||||||
|
const commonPrescriptionPrefix = 'common-prescription/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前登录的诊所信息
|
* 获取当前登录的诊所信息
|
||||||
* @param data
|
* @param data
|
||||||
@@ -26,3 +28,143 @@ export async function getChatMessageRegisterInfoApi(data: any = {}) {
|
|||||||
// return requestClient.post<any>(`${prefix}messages-by-room-id`, { params: data });
|
// return requestClient.post<any>(`${prefix}messages-by-room-id`, { params: data });
|
||||||
return requestClient.get<any>(`${prefix}chat-register-info`, { params: data });
|
return requestClient.get<any>(`${prefix}chat-register-info`, { params: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 常用方管理 API ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方药品数据结构(西药/中成药)
|
||||||
|
* @description 西药药品的详细信息
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionWestDrug {
|
||||||
|
/** 药品ID */
|
||||||
|
drug_id?: number;
|
||||||
|
/** 药品ID(兼容字段) */
|
||||||
|
id?: number;
|
||||||
|
/** 药品名称 */
|
||||||
|
drug_name: string;
|
||||||
|
/** 每次用量 */
|
||||||
|
number?: number;
|
||||||
|
/** 购买数量 */
|
||||||
|
select_number?: number;
|
||||||
|
/** 单价 */
|
||||||
|
price?: number;
|
||||||
|
/** 使用时间ID */
|
||||||
|
time_id?: number;
|
||||||
|
/** 使用类型ID */
|
||||||
|
type_id?: number;
|
||||||
|
/** 使用频率ID */
|
||||||
|
frequency_id?: number;
|
||||||
|
/** 单位ID */
|
||||||
|
unit_id?: number;
|
||||||
|
/** 药品图片 */
|
||||||
|
image?: string;
|
||||||
|
/** 说明书 */
|
||||||
|
instruction?: string;
|
||||||
|
/** 使用时间信息 */
|
||||||
|
use_num?: { name?: string };
|
||||||
|
/** 使用类型信息 */
|
||||||
|
use_type?: { name?: string };
|
||||||
|
/** 使用频率信息 */
|
||||||
|
use_frequency?: { name?: string };
|
||||||
|
/** 单位信息 */
|
||||||
|
unit?: { name?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方药品数据结构(中药/颗粒药)
|
||||||
|
* @description 中药/颗粒药药品的详细信息
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionChineseDrug {
|
||||||
|
/** 药品ID */
|
||||||
|
drug_id?: number;
|
||||||
|
/** 药品ID(兼容字段) */
|
||||||
|
id?: number;
|
||||||
|
/** 药品名称 */
|
||||||
|
drug_name: string;
|
||||||
|
/** 克数/用量 */
|
||||||
|
number?: number;
|
||||||
|
/** 单价 */
|
||||||
|
price?: number;
|
||||||
|
/** 用法ID */
|
||||||
|
way_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存西药常用方
|
||||||
|
* @description 将当前处方保存为西药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
* @param data 常用方数据
|
||||||
|
*/
|
||||||
|
export async function saveWestCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionWestDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(`${commonPrescriptionPrefix}save-west`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存中药常用方
|
||||||
|
* @description 将当前处方保存为中药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
* @param data 常用方数据
|
||||||
|
*/
|
||||||
|
export async function saveChineseCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
/** 剂量/天数(可选,默认7) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选,默认2) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}save-chinese`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存颗粒药常用方
|
||||||
|
* @description 将当前处方保存为颗粒药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
* @param data 常用方数据
|
||||||
|
*/
|
||||||
|
export async function saveGranularCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
/** 剂量/天数(可选,默认7) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选,默认2) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}save-granular`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
DownOutlined,
|
DownOutlined,
|
||||||
MinusOutlined,
|
MinusOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
|
SaveOutlined,
|
||||||
UpOutlined,
|
UpOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
Descriptions,
|
Descriptions,
|
||||||
Image,
|
Image,
|
||||||
ImagePreviewGroup,
|
ImagePreviewGroup,
|
||||||
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
message,
|
message,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
@@ -31,6 +33,10 @@ import {
|
|||||||
TimelineItem,
|
TimelineItem,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
saveChineseCommonPrescriptionApi,
|
||||||
|
saveWestCommonPrescriptionApi,
|
||||||
|
} from '#/views/business/chat/api';
|
||||||
import { usePrescriptionStore } from '#/store/prescription';
|
import { usePrescriptionStore } from '#/store/prescription';
|
||||||
// import { getTraditionalChineseMedicineAllApi } from '#/views/doctor/doctor-reception/api';
|
// import { getTraditionalChineseMedicineAllApi } from '#/views/doctor/doctor-reception/api';
|
||||||
// 导入子组件
|
// 导入子组件
|
||||||
@@ -38,6 +44,8 @@ import DiagnosisModal from '#/views/doctor/doctor-reception/components/Diagnosis
|
|||||||
import DoctorOrderModal from '#/views/doctor/doctor-reception/components/DoctorOrderModal.vue';
|
import DoctorOrderModal from '#/views/doctor/doctor-reception/components/DoctorOrderModal.vue';
|
||||||
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
|
||||||
import WesternModal from '#/views/doctor/doctor-reception/components/WesternModal.vue';
|
import WesternModal from '#/views/doctor/doctor-reception/components/WesternModal.vue';
|
||||||
|
// 常用方选择弹窗组件
|
||||||
|
import CommonPrescriptionModal from '#/views/doctor/doctor-reception/components/CommonPrescriptionModal.vue';
|
||||||
import StoreConfirmModal from './StoreConfirmModal.vue';
|
import StoreConfirmModal from './StoreConfirmModal.vue';
|
||||||
|
|
||||||
const splitString = (input: string) => input.split(',');
|
const splitString = (input: string) => input.split(',');
|
||||||
@@ -49,6 +57,14 @@ const previewImage = ref([]);
|
|||||||
const showNewDrugModal = ref(false);
|
const showNewDrugModal = ref(false);
|
||||||
const doctorSecondSignModal = ref(false);
|
const doctorSecondSignModal = ref(false);
|
||||||
|
|
||||||
|
// ==================== 保存常用方相关状态 ====================
|
||||||
|
/** 保存常用方弹窗是否显示 */
|
||||||
|
const showSaveCommonPrescriptionModal = ref(false);
|
||||||
|
/** 常用方名称输入 */
|
||||||
|
const commonPrescriptionName = ref('');
|
||||||
|
/** 是否正在保存常用方 */
|
||||||
|
const isSavingCommonPrescription = ref(false);
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
{ label: '中药', value: 1 },
|
{ label: '中药', value: 1 },
|
||||||
{ label: '中成(西)药', value: 2 },
|
{ label: '中成(西)药', value: 2 },
|
||||||
@@ -124,6 +140,11 @@ const [StoreConfirmModalComponent, storeConfirmModalApi] = useVbenModal({
|
|||||||
connectedComponent: StoreConfirmModal,
|
connectedComponent: StoreConfirmModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================== 常用方选择弹窗 ====================
|
||||||
|
const [CommonPrescriptionModals, CommonPrescriptionModalApi] = useVbenModal({
|
||||||
|
connectedComponent: CommonPrescriptionModal,
|
||||||
|
});
|
||||||
|
|
||||||
const setVisible = (value: boolean, instruction = ''): void => {
|
const setVisible = (value: boolean, instruction = ''): void => {
|
||||||
previewImage.value = [];
|
previewImage.value = [];
|
||||||
if (instruction === '') {
|
if (instruction === '') {
|
||||||
@@ -293,6 +314,204 @@ const filterOption = (input: string, option: any) => {
|
|||||||
option.code.includes(input)
|
option.code.includes(input)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 选择常用方相关方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开常用方选择弹窗
|
||||||
|
* @description 打开常用方弹窗,选择后自动填充药品到处方中
|
||||||
|
*/
|
||||||
|
function openCommonPrescriptionModal() {
|
||||||
|
CommonPrescriptionModalApi.setData({
|
||||||
|
type: prescriptionStore.activeCategory,
|
||||||
|
onSelect: handleSelectCommonPrescription,
|
||||||
|
});
|
||||||
|
CommonPrescriptionModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理选择常用方
|
||||||
|
* @param data 常用方数据,包含prescription和recipes
|
||||||
|
* @param type 类型:1-中药,2-西药,3-颗粒药
|
||||||
|
* @description 选择常用方后,将药品列表填充到当前处方中
|
||||||
|
*/
|
||||||
|
function handleSelectCommonPrescription(data: any, type: number) {
|
||||||
|
const { prescription, recipes } = data;
|
||||||
|
|
||||||
|
// 根据类型处理不同的药品数据
|
||||||
|
if (type === 2) {
|
||||||
|
// 西药(中成药)处方
|
||||||
|
recipes.forEach((recipe: any) => {
|
||||||
|
const newProduct = {
|
||||||
|
index_id: recipe.drug_id || recipe.id,
|
||||||
|
id: recipe.drug_id || recipe.id,
|
||||||
|
drug_name: recipe.drug_name,
|
||||||
|
number: recipe.number || 1,
|
||||||
|
use_num: recipe.use_time,
|
||||||
|
use_type: recipe.use_type,
|
||||||
|
use_frequency: recipe.use_frequency,
|
||||||
|
unit: recipe.west_unit,
|
||||||
|
price: recipe.price || 0,
|
||||||
|
time_id: recipe.time_id,
|
||||||
|
type_id: recipe.type_id,
|
||||||
|
frequency_id: recipe.frequency_id,
|
||||||
|
unit_id: recipe.unit_id,
|
||||||
|
image: recipe.image,
|
||||||
|
instruction: recipe.instruction,
|
||||||
|
type: recipe.type,
|
||||||
|
select_number: recipe.select_number || 1,
|
||||||
|
};
|
||||||
|
// 检查是否已存在
|
||||||
|
const existItem = prescriptionStore.currentDrugs.find(
|
||||||
|
(item: any) => item.id === newProduct.id,
|
||||||
|
);
|
||||||
|
if (!existItem) {
|
||||||
|
prescriptionStore.currentDrugs.push(newProduct);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 中药/颗粒药处方
|
||||||
|
recipes.forEach((recipe: any) => {
|
||||||
|
const newProduct = {
|
||||||
|
index_id: recipe.drug_id || recipe.id,
|
||||||
|
id: recipe.drug_id || recipe.id,
|
||||||
|
drug_name: recipe.drug_name || recipe.name,
|
||||||
|
number: recipe.number || 1,
|
||||||
|
price: recipe.price || 0,
|
||||||
|
way_id: recipe.way_id || 0,
|
||||||
|
select_number: 1,
|
||||||
|
};
|
||||||
|
// 检查是否已存在
|
||||||
|
const existItem = prescriptionStore.currentDrugs.find(
|
||||||
|
(item: any) => item.id === newProduct.id,
|
||||||
|
);
|
||||||
|
if (!existItem) {
|
||||||
|
prescriptionStore.currentDrugs.push(newProduct);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新诊断和医嘱
|
||||||
|
if (prescription.clinical_diagnose) {
|
||||||
|
prescriptionStore.diagnosis = prescription.clinical_diagnose;
|
||||||
|
}
|
||||||
|
if (prescription.doctor_order) {
|
||||||
|
prescriptionStore.medicalAdvice = prescription.doctor_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到本地存储
|
||||||
|
prescriptionStore.saveToLocalStorage();
|
||||||
|
message.success('已选择常用方,药品已填充到处方中');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 保存常用方相关方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开保存常用方弹窗
|
||||||
|
* @description 检查是否有药品可保存,然后打开弹窗
|
||||||
|
*/
|
||||||
|
const openSaveCommonPrescriptionModal = () => {
|
||||||
|
// 检查是否有药品
|
||||||
|
if (prescriptionStore.currentDrugs.length === 0) {
|
||||||
|
message.warning('请先添加药品后再保存为常用方');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 清空输入框
|
||||||
|
commonPrescriptionName.value = '';
|
||||||
|
// 打开弹窗
|
||||||
|
showSaveCommonPrescriptionModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前处方为常用方
|
||||||
|
* @description 根据当前药品类型(中药/西药)调用不同的保存接口
|
||||||
|
* 后端会自动根据药品详细信息创建 recipe 记录
|
||||||
|
*/
|
||||||
|
const saveAsCommonPrescription = async () => {
|
||||||
|
// 验证名称
|
||||||
|
if (!commonPrescriptionName.value.trim()) {
|
||||||
|
message.warning('请输入常用方名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否有药品
|
||||||
|
if (prescriptionStore.currentDrugs.length === 0) {
|
||||||
|
message.warning('没有可保存的药品');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSavingCommonPrescription.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 根据处方类型选择不同的保存接口
|
||||||
|
if (prescriptionStore.activeCategory === 1) {
|
||||||
|
// 中药处方 - 构建药品详细信息数组
|
||||||
|
const drugs = prescriptionStore.currentDrugs.map((drug) => ({
|
||||||
|
drug_id: drug.id,
|
||||||
|
drug_name: drug.drug_name,
|
||||||
|
number: drug.number || 1,
|
||||||
|
price: drug.price || 0,
|
||||||
|
way_id: drug.way_id || 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await saveChineseCommonPrescriptionApi({
|
||||||
|
name: commonPrescriptionName.value.trim(),
|
||||||
|
drugs,
|
||||||
|
store_id: prescriptionStore.myStoreId,
|
||||||
|
clinical_diagnose: prescriptionStore.diagnosis || '',
|
||||||
|
doctor_order: prescriptionStore.medicalAdvice || '',
|
||||||
|
category: String(prescriptionStore.category),
|
||||||
|
dosage: prescriptionStore.dosage || 7,
|
||||||
|
day_dosage: prescriptionStore.dayDosage || 2,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 西药(中成药)处方 - 构建药品详细信息数组
|
||||||
|
const drugs = prescriptionStore.currentDrugs.map((drug) => ({
|
||||||
|
drug_id: drug.id,
|
||||||
|
drug_name: drug.drug_name,
|
||||||
|
number: drug.number || 1,
|
||||||
|
select_number: drug.select_number || 1,
|
||||||
|
price: drug.price || 0,
|
||||||
|
time_id: drug.time_id || 0,
|
||||||
|
type_id: drug.type_id || 0,
|
||||||
|
frequency_id: drug.frequency_id || 0,
|
||||||
|
unit_id: drug.unit_id || 0,
|
||||||
|
image: drug.image || '',
|
||||||
|
instruction: drug.instruction || '',
|
||||||
|
use_num: drug.use_num,
|
||||||
|
use_type: drug.use_type,
|
||||||
|
use_frequency: drug.use_frequency,
|
||||||
|
unit: drug.unit,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await saveWestCommonPrescriptionApi({
|
||||||
|
name: commonPrescriptionName.value.trim(),
|
||||||
|
drugs,
|
||||||
|
store_id: prescriptionStore.myStoreId,
|
||||||
|
clinical_diagnose: prescriptionStore.diagnosis || '',
|
||||||
|
doctor_order: prescriptionStore.medicalAdvice || '',
|
||||||
|
category: String(prescriptionStore.category),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('常用方保存成功');
|
||||||
|
showSaveCommonPrescriptionModal.value = false;
|
||||||
|
commonPrescriptionName.value = '';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存常用方失败:', error);
|
||||||
|
message.error('保存常用方失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
isSavingCommonPrescription.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消保存常用方
|
||||||
|
*/
|
||||||
|
const cancelSaveCommonPrescription = () => {
|
||||||
|
showSaveCommonPrescriptionModal.value = false;
|
||||||
|
commonPrescriptionName.value = '';
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -597,14 +816,31 @@ const filterOption = (input: string, option: any) => {
|
|||||||
<RadioButton :value="2">医保</RadioButton>
|
<RadioButton :value="2">医保</RadioButton>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
<Button
|
<div class="flex gap-2">
|
||||||
v-if="prescriptionStore.activeCategory !== 1"
|
<!-- 选择常用方按钮 -->
|
||||||
type="primary"
|
<Button
|
||||||
@click="openWesternModal"
|
type="default"
|
||||||
>
|
@click="openCommonPrescriptionModal"
|
||||||
<PlusOutlined />
|
>
|
||||||
添加商品
|
选择常用方
|
||||||
</Button>
|
</Button>
|
||||||
|
<!-- 保存为常用方按钮 -->
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
@click="openSaveCommonPrescriptionModal"
|
||||||
|
>
|
||||||
|
<SaveOutlined />
|
||||||
|
保存为常用方
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="prescriptionStore.activeCategory !== 1"
|
||||||
|
type="primary"
|
||||||
|
@click="openWesternModal"
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
添加商品
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中药药品列表 -->
|
<!-- 中药药品列表 -->
|
||||||
@@ -955,11 +1191,63 @@ const filterOption = (input: string, option: any) => {
|
|||||||
<p>
|
<p>
|
||||||
<span>药品名称:{{ drug.drug_name }}</span>
|
<span>药品名称:{{ drug.drug_name }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<!-- 西药用法显示/编辑模式 -->
|
||||||
|
<p v-if="prescriptionStore.activeCategory !== 1 && !drug.isEditing">
|
||||||
<span>用法:{{
|
<span>用法:{{
|
||||||
`${drug.use_type?.name},${drug.use_frequency?.name},${drug.use_num?.name},每次${drug.number}${drug.unit?.name}`
|
`${drug.use_type?.name},${drug.use_frequency?.name},${drug.use_num?.name},每次${drug.number}${drug.unit?.name}`
|
||||||
}}</span>
|
}}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<div v-else-if="prescriptionStore.activeCategory !== 1 && drug.isEditing" class="edit-usage-form">
|
||||||
|
<span>用法:</span>
|
||||||
|
<Select
|
||||||
|
:value="drug.use_type?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 80px"
|
||||||
|
@change="(val) => prescriptionStore.updateDrugUsage(index, 'use_type', prescriptionStore.drugUseType.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in prescriptionStore.drugUseType" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
:value="drug.use_frequency?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 100px; margin-left: 4px"
|
||||||
|
@change="(val) => prescriptionStore.updateDrugUsage(index, 'use_frequency', prescriptionStore.drugUseFrequency.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in prescriptionStore.drugUseFrequency" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
:value="drug.use_num?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 80px; margin-left: 4px"
|
||||||
|
@change="(val) => prescriptionStore.updateDrugUsage(index, 'use_num', prescriptionStore.drugTime.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in prescriptionStore.drugTime" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<span style="margin-left: 4px">每次</span>
|
||||||
|
<InputNumber
|
||||||
|
:value="drug.number"
|
||||||
|
:min="1"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px; margin-left: 4px"
|
||||||
|
@change="(val) => prescriptionStore.updateDrugUsage(index, 'number', val)"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
:value="drug.unit?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px; margin-left: 4px"
|
||||||
|
@change="(val) => prescriptionStore.updateDrugUsage(index, 'unit', prescriptionStore.drugUnit.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in prescriptionStore.drugUnit" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="quantity-control">
|
<div class="quantity-control">
|
||||||
@@ -980,6 +1268,21 @@ const filterOption = (input: string, option: any) => {
|
|||||||
</div>
|
</div>
|
||||||
<span>{{ drug.price }}</span>
|
<span>{{ drug.price }}</span>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- 西药编辑/保存按钮 -->
|
||||||
|
<Button
|
||||||
|
v-if="prescriptionStore.activeCategory !== 1 && !drug.isEditing"
|
||||||
|
type="link"
|
||||||
|
@click="prescriptionStore.toggleEditDrug(index)"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="prescriptionStore.activeCategory !== 1 && drug.isEditing"
|
||||||
|
type="link"
|
||||||
|
@click="prescriptionStore.saveEditDrug(index)"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
@click="prescriptionStore.removeDrug(index)"
|
@click="prescriptionStore.removeDrug(index)"
|
||||||
@@ -1280,6 +1583,33 @@ const filterOption = (input: string, option: any) => {
|
|||||||
要把【{{ prescriptionStore.newDrugInfo.name }}】添加到清单吗?
|
要把【{{ prescriptionStore.newDrugInfo.name }}】添加到清单吗?
|
||||||
</AntModal>
|
</AntModal>
|
||||||
|
|
||||||
|
<!-- 保存常用方确认模态框 -->
|
||||||
|
<AntModal
|
||||||
|
v-model:open="showSaveCommonPrescriptionModal"
|
||||||
|
title="保存为常用方"
|
||||||
|
:confirm-loading="isSavingCommonPrescription"
|
||||||
|
@ok="saveAsCommonPrescription"
|
||||||
|
@cancel="cancelSaveCommonPrescription"
|
||||||
|
>
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="mb-4 text-gray-600">
|
||||||
|
将当前处方({{ prescriptionStore.currentDrugs.length }}种药品)保存为常用方模板
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 whitespace-nowrap">常用方名称:</span>
|
||||||
|
<Input
|
||||||
|
v-model:value="commonPrescriptionName"
|
||||||
|
placeholder="请输入常用方名称"
|
||||||
|
:maxlength="50"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-xs text-gray-400">
|
||||||
|
类型:{{ prescriptionStore.activeCategory === 1 ? '中药' : '西药(中成药)' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</AntModal>
|
||||||
|
|
||||||
<!-- 二次签名确认模态框 -->
|
<!-- 二次签名确认模态框 -->
|
||||||
<AntModal
|
<AntModal
|
||||||
v-model:open="doctorSecondSignModal"
|
v-model:open="doctorSecondSignModal"
|
||||||
@@ -1319,6 +1649,8 @@ const filterOption = (input: string, option: any) => {
|
|||||||
<DoctorOrderModals />
|
<DoctorOrderModals />
|
||||||
<PrescriptionDetailModal />
|
<PrescriptionDetailModal />
|
||||||
<StoreConfirmModalComponent />
|
<StoreConfirmModalComponent />
|
||||||
|
<!-- 常用方选择弹窗 -->
|
||||||
|
<CommonPrescriptionModals />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -143,3 +143,188 @@ export async function getTraditionalChineseMedicineAllApi() {
|
|||||||
`${prefix}get-traditional-chinese-medicine-all`,
|
`${prefix}get-traditional-chinese-medicine-all`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 常用方管理 API ====================
|
||||||
|
|
||||||
|
const commonPrescriptionPrefix = 'common-prescription/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方列表响应数据结构
|
||||||
|
* @description 包含西药、中药、颗粒药三种类型的常用方
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionData {
|
||||||
|
/** 西药常用方列表 */
|
||||||
|
west_prescription: any[];
|
||||||
|
/** 西药详细药品信息 */
|
||||||
|
west: any[][];
|
||||||
|
/** 中药常用方列表 */
|
||||||
|
chin_prescription: any[];
|
||||||
|
/** 中药详细药品信息 */
|
||||||
|
chinese: any[][];
|
||||||
|
/** 颗粒药常用方列表 */
|
||||||
|
granular_prescription: any[];
|
||||||
|
/** 颗粒药详细药品信息 */
|
||||||
|
granular: any[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取常用方列表
|
||||||
|
* @description 获取当前医生的所有常用方(西药/中药/颗粒药)
|
||||||
|
* @param storeId 诊所ID(可选)
|
||||||
|
*/
|
||||||
|
export async function getCommonPrescriptionListApi(storeId?: number) {
|
||||||
|
return requestClient.get<CommonPrescriptionData>(
|
||||||
|
`${commonPrescriptionPrefix}list`,
|
||||||
|
{ params: { store_id: storeId } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取常用方详情
|
||||||
|
* @description 根据ID和类型获取常用方的详细信息
|
||||||
|
* @param id 常用方ID
|
||||||
|
* @param type 类型:west-西药,chinese-中药,granular-颗粒药
|
||||||
|
*/
|
||||||
|
export async function getCommonPrescriptionDetailApi(
|
||||||
|
id: number,
|
||||||
|
type: string,
|
||||||
|
) {
|
||||||
|
return requestClient.get<any[]>(`${commonPrescriptionPrefix}detail`, {
|
||||||
|
params: { id, type },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方药品数据结构(西药/中成药)
|
||||||
|
* @description 西药药品的详细信息
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionWestDrug {
|
||||||
|
/** 药品ID */
|
||||||
|
drug_id?: number;
|
||||||
|
/** 药品ID(兼容字段) */
|
||||||
|
id?: number;
|
||||||
|
/** 药品名称 */
|
||||||
|
drug_name: string;
|
||||||
|
/** 每次用量 */
|
||||||
|
number?: number;
|
||||||
|
/** 购买数量 */
|
||||||
|
select_number?: number;
|
||||||
|
/** 单价 */
|
||||||
|
price?: number;
|
||||||
|
/** 使用时间ID */
|
||||||
|
time_id?: number;
|
||||||
|
/** 使用类型ID */
|
||||||
|
type_id?: number;
|
||||||
|
/** 使用频率ID */
|
||||||
|
frequency_id?: number;
|
||||||
|
/** 单位ID */
|
||||||
|
unit_id?: number;
|
||||||
|
/** 药品图片 */
|
||||||
|
image?: string;
|
||||||
|
/** 说明书 */
|
||||||
|
instruction?: string;
|
||||||
|
/** 使用时间信息 */
|
||||||
|
use_num?: { name?: string };
|
||||||
|
/** 使用类型信息 */
|
||||||
|
use_type?: { name?: string };
|
||||||
|
/** 使用频率信息 */
|
||||||
|
use_frequency?: { name?: string };
|
||||||
|
/** 单位信息 */
|
||||||
|
unit?: { name?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方药品数据结构(中药/颗粒药)
|
||||||
|
* @description 中药/颗粒药药品的详细信息
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionChineseDrug {
|
||||||
|
/** 药品ID */
|
||||||
|
drug_id?: number;
|
||||||
|
/** 药品ID(兼容字段) */
|
||||||
|
id?: number;
|
||||||
|
/** 药品名称 */
|
||||||
|
drug_name: string;
|
||||||
|
/** 克数/用量 */
|
||||||
|
number?: number;
|
||||||
|
/** 单价 */
|
||||||
|
price?: number;
|
||||||
|
/** 用法ID */
|
||||||
|
way_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存西药常用方
|
||||||
|
* @description 将当前处方保存为西药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
*/
|
||||||
|
export async function saveWestCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionWestDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(`${commonPrescriptionPrefix}save-west`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存中药常用方
|
||||||
|
* @description 将当前处方保存为中药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
*/
|
||||||
|
export async function saveChineseCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
/** 剂量/天数(可选,默认7) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选,默认2) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}save-chinese`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存颗粒药常用方
|
||||||
|
* @description 将当前处方保存为颗粒药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
*/
|
||||||
|
export async function saveGranularCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
/** 剂量/天数(可选,默认7) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选,默认2) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}save-granular`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,347 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 常用方选择弹窗组件
|
||||||
|
*
|
||||||
|
* @description 用于在开处方时快速选择已保存的常用方模板
|
||||||
|
* - 支持西药、中药、颗粒药三种类型
|
||||||
|
* - 选择后会自动填充药品列表
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Col,
|
||||||
|
Collapse,
|
||||||
|
CollapsePanel,
|
||||||
|
Empty,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Spin,
|
||||||
|
Tag,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getCommonPrescriptionListApi,
|
||||||
|
type CommonPrescriptionData,
|
||||||
|
} from '#/views/doctor/doctor-reception/api';
|
||||||
|
|
||||||
|
// ==================== 响应式数据 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方数据
|
||||||
|
*/
|
||||||
|
const commonPrescriptionData = ref<CommonPrescriptionData | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载状态
|
||||||
|
*/
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前选中的药品类型
|
||||||
|
* 1-西药,2-中药,3-颗粒药
|
||||||
|
*/
|
||||||
|
const currentType = ref(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调函数:选择常用方后的回调
|
||||||
|
*/
|
||||||
|
let onSelectCallback: ((data: any, type: number) => void) | null = null;
|
||||||
|
|
||||||
|
// ==================== Modal 配置 ====================
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
footer: false,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
// 获取传入的参数
|
||||||
|
const data = modalApi.getData<{
|
||||||
|
type?: number;
|
||||||
|
onSelect?: (data: any, type: number) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
if (data?.type) {
|
||||||
|
currentType.value = data.type;
|
||||||
|
}
|
||||||
|
if (data?.onSelect) {
|
||||||
|
onSelectCallback = data.onSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载常用方列表
|
||||||
|
loadCommonPrescriptionList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 方法定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载常用方列表
|
||||||
|
*/
|
||||||
|
async function loadCommonPrescriptionList() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getCommonPrescriptionListApi();
|
||||||
|
commonPrescriptionData.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取常用方列表失败:', error);
|
||||||
|
message.error('获取常用方列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择常用方
|
||||||
|
* @param prescription 常用方信息
|
||||||
|
* @param recipes 药品列表
|
||||||
|
* @param type 类型:1-西药,2-中药,3-颗粒药
|
||||||
|
*/
|
||||||
|
function handleSelectPrescription(
|
||||||
|
prescription: any,
|
||||||
|
recipes: any[],
|
||||||
|
type: number,
|
||||||
|
) {
|
||||||
|
// 调用回调函数,将选中的常用方数据传递给父组件
|
||||||
|
if (onSelectCallback) {
|
||||||
|
onSelectCallback(
|
||||||
|
{
|
||||||
|
prescription,
|
||||||
|
recipes,
|
||||||
|
},
|
||||||
|
type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success('已选择常用方,药品已填充到处方中');
|
||||||
|
modalApi.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取药品名称列表(用于展示)
|
||||||
|
* @param recipes 药品列表
|
||||||
|
* @param nameField 名称字段
|
||||||
|
* @param showNumber 是否显示克数(中药使用)
|
||||||
|
*/
|
||||||
|
function getDrugNames(recipes: any[], nameField = 'drug_name', showNumber = false): string {
|
||||||
|
if (!recipes || recipes.length === 0) return '暂无药品';
|
||||||
|
return recipes.map((item) => {
|
||||||
|
const name = item[nameField] || item.name;
|
||||||
|
if (showNumber && item.number) {
|
||||||
|
return `${name}(${item.number}g)`;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}).join('、');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取药品数量
|
||||||
|
* @param recipes 药品列表
|
||||||
|
*/
|
||||||
|
function getDrugCount(recipes: any[]): number {
|
||||||
|
return recipes?.length || 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[70%]" title="选择常用方">
|
||||||
|
<Page>
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<div v-if="!commonPrescriptionData" class="py-8 text-center">
|
||||||
|
<Empty description="加载中..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<Row :gutter="16">
|
||||||
|
<!-- 西药常用方 - 只在西药Tab时显示 -->
|
||||||
|
<Col v-if="currentType === 2" :span="24" class="mb-6">
|
||||||
|
<div class="rounded-lg border border-border p-4 mb-4">
|
||||||
|
<h3 class="flex items-center gap-2 mb-4 text-base font-semibold">
|
||||||
|
<Tag color="blue">西药</Tag>
|
||||||
|
西药常用方
|
||||||
|
<span class="text-sm font-normal text-muted-foreground"
|
||||||
|
>({{
|
||||||
|
commonPrescriptionData.west_prescription?.length || 0
|
||||||
|
}})</span
|
||||||
|
>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
commonPrescriptionData.west_prescription &&
|
||||||
|
commonPrescriptionData.west_prescription.length > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<Collapse>
|
||||||
|
<CollapsePanel
|
||||||
|
v-for="(item, index) in commonPrescriptionData.west_prescription"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<span class="font-medium">{{
|
||||||
|
item.name || `西药处方 ${index + 1}`
|
||||||
|
}}</span>
|
||||||
|
<Tag color="blue" class="ml-auto mr-4"
|
||||||
|
>{{
|
||||||
|
getDrugCount(commonPrescriptionData.west[index])
|
||||||
|
}}种药品</Tag
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p v-if="item.clinical_diagnose" class="mb-2 leading-relaxed">
|
||||||
|
<span class="font-medium text-muted-foreground">临床诊断:</span>
|
||||||
|
<span>{{ item.clinical_diagnose }}</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="item.doctor_order" class="mb-2 leading-relaxed">
|
||||||
|
<span class="font-medium text-muted-foreground">医嘱:</span>
|
||||||
|
<span>{{ item.doctor_order }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-2 leading-relaxed">
|
||||||
|
<span class="font-medium text-muted-foreground">药品:</span>
|
||||||
|
<span class="text-primary">{{
|
||||||
|
getDrugNames(commonPrescriptionData.west[index])
|
||||||
|
}}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex justify-end mt-4 pt-4 border-t border-border">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
handleSelectPrescription(
|
||||||
|
item,
|
||||||
|
commonPrescriptionData.west[index],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
使用此常用方
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Empty v-else description="暂无西药常用方" />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<!-- 中药常用方 - 只在中药Tab时显示 -->
|
||||||
|
<Col v-if="currentType === 1" :span="24" class="mb-6">
|
||||||
|
<div class="rounded-lg border border-border p-4 mb-4">
|
||||||
|
<h3 class="flex items-center gap-2 mb-4 text-base font-semibold">
|
||||||
|
<Tag color="green">中药</Tag>
|
||||||
|
中药常用方
|
||||||
|
<span class="text-sm font-normal text-muted-foreground"
|
||||||
|
>({{
|
||||||
|
commonPrescriptionData.chin_prescription?.length || 0
|
||||||
|
}})</span
|
||||||
|
>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
commonPrescriptionData.chin_prescription &&
|
||||||
|
commonPrescriptionData.chin_prescription.length > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<Collapse>
|
||||||
|
<CollapsePanel
|
||||||
|
v-for="(
|
||||||
|
item, index
|
||||||
|
) in commonPrescriptionData.chin_prescription"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<span class="font-medium">{{
|
||||||
|
item.name || `中药处方 ${index + 1}`
|
||||||
|
}}</span>
|
||||||
|
<Tag color="green" class="ml-auto mr-4"
|
||||||
|
>{{
|
||||||
|
getDrugCount(
|
||||||
|
commonPrescriptionData.chinese[index],
|
||||||
|
)
|
||||||
|
}}种药品</Tag
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p v-if="item.clinical_diagnose" class="mb-2 leading-relaxed">
|
||||||
|
<span class="font-medium text-muted-foreground">临床诊断:</span>
|
||||||
|
<span>{{ item.clinical_diagnose }}</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="item.doctor_order" class="mb-2 leading-relaxed">
|
||||||
|
<span class="font-medium text-muted-foreground">医嘱:</span>
|
||||||
|
<span>{{ item.doctor_order }}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-2 leading-relaxed">
|
||||||
|
<span class="font-medium text-muted-foreground">药品:</span>
|
||||||
|
<span class="text-primary">{{
|
||||||
|
getDrugNames(
|
||||||
|
commonPrescriptionData.chinese[index],
|
||||||
|
'drug_name',
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex justify-end mt-4 pt-4 border-t border-border">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
handleSelectPrescription(
|
||||||
|
item,
|
||||||
|
commonPrescriptionData.chinese[index],
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
使用此常用方
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Empty v-else description="暂无中药常用方" />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</Page>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.ant-collapse) {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.ant-collapse-item {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-collapse-content {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -52,6 +52,8 @@ import PrescriptionDetail from './components/PrescriptionDetail.vue';
|
|||||||
import WesternModal from './components/WesternModal.vue';
|
import WesternModal from './components/WesternModal.vue';
|
||||||
import RefusalOfTreatmentModal
|
import RefusalOfTreatmentModal
|
||||||
from "#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue";
|
from "#/views/doctor/doctor-reception/components/RefusalOfTreatmentModal.vue";
|
||||||
|
// 常用方选择弹窗组件
|
||||||
|
import CommonPrescriptionModal from './components/CommonPrescriptionModal.vue';
|
||||||
|
|
||||||
interface Patient {
|
interface Patient {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -320,6 +322,23 @@ const selectPatient = (patient: Patient, isUpdateTabType = true) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
activePatient.value = patient.user_patient;
|
activePatient.value = patient.user_patient;
|
||||||
|
|
||||||
|
// 恢复之前保存的 activeCategory
|
||||||
|
const savedCategory = localStorage.getItem(`activeCategory${patient.user_patient?.id}`);
|
||||||
|
if (savedCategory) {
|
||||||
|
activeCategory.value = Number.parseInt(savedCategory);
|
||||||
|
} else {
|
||||||
|
// 如果没有保存的值,检查两个存储键哪个有数据
|
||||||
|
const chineseData = localStorage.getItem(`prescriptionData_1_${patient.user_patient?.id}`);
|
||||||
|
const westData = localStorage.getItem(`prescriptionData_2_${patient.user_patient?.id}`);
|
||||||
|
if (chineseData && JSON.parse(chineseData).length > 0) {
|
||||||
|
activeCategory.value = 1;
|
||||||
|
} else if (westData && JSON.parse(westData).length > 0) {
|
||||||
|
activeCategory.value = 2;
|
||||||
|
}
|
||||||
|
// 否则保持默认值
|
||||||
|
}
|
||||||
|
|
||||||
getPatientItem(patient.id).then((value) => {
|
getPatientItem(patient.id).then((value) => {
|
||||||
patientInfo.value = value;
|
patientInfo.value = value;
|
||||||
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
|
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
|
||||||
@@ -348,10 +367,41 @@ const decrement = (index: number) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存到本地存储
|
// 切换药品用法编辑状态
|
||||||
|
const toggleEditDrug = (index: number) => {
|
||||||
|
currentDrugs.value[index].isEditing = !currentDrugs.value[index].isEditing;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存药品用法编辑
|
||||||
|
const saveEditDrug = (index: number) => {
|
||||||
|
const drug = currentDrugs.value[index];
|
||||||
|
// 更新 ID 字段
|
||||||
|
drug.type_id = drug.use_type?.id;
|
||||||
|
drug.frequency_id = drug.use_frequency?.id;
|
||||||
|
drug.time_id = drug.use_num?.id;
|
||||||
|
drug.unit_id = drug.unit?.id;
|
||||||
|
drug.isEditing = false;
|
||||||
|
updateLocalStorage();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新药品用法字段
|
||||||
|
const updateDrugUsage = (index: number, field: string, value: any) => {
|
||||||
|
currentDrugs.value[index][field] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取药品数据的存储key(按类型分开存储)
|
||||||
|
* @param patientId 患者ID
|
||||||
|
* @param category 类型:1-中药,2-西药
|
||||||
|
*/
|
||||||
|
const getStorageKey = (patientId: number | string | undefined, category: number) => {
|
||||||
|
return `prescriptionData_${category}_${patientId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存到本地存储(按类型分开存储)
|
||||||
const saveToLocalStorage = () => {
|
const saveToLocalStorage = () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
`prescriptionData${activePatient.value?.id}`,
|
getStorageKey(activePatient.value?.id, activeCategory.value),
|
||||||
JSON.stringify(currentDrugs.value),
|
JSON.stringify(currentDrugs.value),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -364,11 +414,11 @@ const removeDrug = (index: number) => {
|
|||||||
updateLocalStorage();
|
updateLocalStorage();
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 修改缓存的处方信息
|
* 修改缓存的处方信息(按类型分开存储)
|
||||||
*/
|
*/
|
||||||
const updateLocalStorage = () => {
|
const updateLocalStorage = () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
`prescriptionData${activePatient.value?.id}`,
|
getStorageKey(activePatient.value?.id, activeCategory.value),
|
||||||
JSON.stringify(currentDrugs.value),
|
JSON.stringify(currentDrugs.value),
|
||||||
);
|
);
|
||||||
getCurrentDrugs();
|
getCurrentDrugs();
|
||||||
@@ -376,11 +426,11 @@ const updateLocalStorage = () => {
|
|||||||
|
|
||||||
const currentDrugs = ref([]);
|
const currentDrugs = ref([]);
|
||||||
/**
|
/**
|
||||||
* 获取缓存的处方信息
|
* 获取缓存的处方信息(按类型分开存储)
|
||||||
*/
|
*/
|
||||||
const getCurrentDrugs = () => {
|
const getCurrentDrugs = () => {
|
||||||
currentDrugs.value = JSON.parse(
|
currentDrugs.value = JSON.parse(
|
||||||
localStorage.getItem(`prescriptionData${activePatient.value?.id}`) || '[]',
|
localStorage.getItem(getStorageKey(activePatient.value?.id, activeCategory.value)) || '[]',
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
getCurrentDrugs();
|
getCurrentDrugs();
|
||||||
@@ -606,6 +656,98 @@ const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
|||||||
const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
|
const [RefusalOfTreatmentModals, RefusalOfTreatmentModalApi] = useVbenModal({
|
||||||
connectedComponent: RefusalOfTreatmentModal,
|
connectedComponent: RefusalOfTreatmentModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================== 常用方弹窗 ====================
|
||||||
|
const [CommonPrescriptionModals, CommonPrescriptionModalApi] = useVbenModal({
|
||||||
|
connectedComponent: CommonPrescriptionModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开常用方选择弹窗
|
||||||
|
* @description 打开常用方弹窗,选择后自动填充药品到处方中
|
||||||
|
*/
|
||||||
|
function openCommonPrescriptionModal() {
|
||||||
|
CommonPrescriptionModalApi.setData({
|
||||||
|
type: activeCategory.value,
|
||||||
|
onSelect: handleSelectCommonPrescription,
|
||||||
|
});
|
||||||
|
CommonPrescriptionModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理选择常用方
|
||||||
|
* @param data 常用方数据,包含prescription和recipes
|
||||||
|
* @param type 类型:1-西药,2-中药,3-颗粒药
|
||||||
|
* @description 选择常用方后,将药品列表填充到当前处方中
|
||||||
|
*/
|
||||||
|
function handleSelectCommonPrescription(data: any, type: number) {
|
||||||
|
const { prescription, recipes } = data;
|
||||||
|
|
||||||
|
// 根据类型处理不同的药品数据
|
||||||
|
if (type === 1) {
|
||||||
|
// 西药处方
|
||||||
|
recipes.forEach((recipe: any) => {
|
||||||
|
const newProduct = {
|
||||||
|
index_id: recipe.id,
|
||||||
|
id: recipe.id,
|
||||||
|
drug_name: recipe.drug_name,
|
||||||
|
number: recipe.number || 1,
|
||||||
|
use_num: recipe.use_time,
|
||||||
|
use_type: recipe.use_type,
|
||||||
|
use_frequency: recipe.use_frequency,
|
||||||
|
unit: recipe.west_unit,
|
||||||
|
price: recipe.price || 0,
|
||||||
|
time_id: recipe.time_id,
|
||||||
|
type_id: recipe.type_id,
|
||||||
|
frequency_id: recipe.frequency_id,
|
||||||
|
unit_id: recipe.unit_id,
|
||||||
|
image: recipe.image,
|
||||||
|
instruction: recipe.instruction,
|
||||||
|
type: recipe.type,
|
||||||
|
select_number: 1,
|
||||||
|
};
|
||||||
|
// 检查是否已存在
|
||||||
|
const existItem = currentDrugs.value.find(
|
||||||
|
(item) => item.id === recipe.id,
|
||||||
|
);
|
||||||
|
if (!existItem) {
|
||||||
|
currentDrugs.value.push(newProduct);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 中药/颗粒药处方 - 使用 drug_id 作为唯一标识
|
||||||
|
recipes.forEach((recipe: any) => {
|
||||||
|
const drugId = recipe.drug_id || recipe.id;
|
||||||
|
const newProduct = {
|
||||||
|
index_id: drugId,
|
||||||
|
id: drugId,
|
||||||
|
drug_name: recipe.drug_name || recipe.name,
|
||||||
|
number: recipe.number || 1,
|
||||||
|
price: recipe.price || 0,
|
||||||
|
way_id: recipe.way_id || 0,
|
||||||
|
select_number: 1,
|
||||||
|
};
|
||||||
|
// 检查是否已存在 - 使用 drug_id 比对
|
||||||
|
const existItem = currentDrugs.value.find(
|
||||||
|
(item) => item.id === drugId,
|
||||||
|
);
|
||||||
|
if (!existItem) {
|
||||||
|
currentDrugs.value.push(newProduct);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新诊断和医嘱
|
||||||
|
if (prescription.clinical_diagnose) {
|
||||||
|
diagnosis.value = prescription.clinical_diagnose;
|
||||||
|
}
|
||||||
|
if (prescription.doctor_order) {
|
||||||
|
medicalAdvice.value = prescription.doctor_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到本地存储
|
||||||
|
updateLocalStorage();
|
||||||
|
}
|
||||||
const openWesternModal = () => {
|
const openWesternModal = () => {
|
||||||
// 打开西药处方模态框逻辑
|
// 打开西药处方模态框逻辑
|
||||||
WesternDrugModalApi.setData({
|
WesternDrugModalApi.setData({
|
||||||
@@ -632,14 +774,28 @@ function splitString(str: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换tab
|
* 切换tab(中药/西药切换)
|
||||||
* @param id
|
* @param id 类型:1-中药,2-西药
|
||||||
|
* @description 切换Tab时:
|
||||||
|
* 1. 先保存当前Tab的药品数据
|
||||||
|
* 2. 更新activeCategory
|
||||||
|
* 3. 加载新Tab对应的药品数据
|
||||||
*/
|
*/
|
||||||
function tabChange(id) {
|
function tabChange(id) {
|
||||||
|
// 先保存当前Tab的药品数据
|
||||||
|
localStorage.setItem(
|
||||||
|
getStorageKey(activePatient.value?.id, activeCategory.value),
|
||||||
|
JSON.stringify(currentDrugs.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新Tab类型
|
||||||
localStorage.setItem(`activeCategory${activePatient.value?.id}`, id);
|
localStorage.setItem(`activeCategory${activePatient.value?.id}`, id);
|
||||||
currentDrugs.value = [];
|
|
||||||
updateLocalStorage();
|
|
||||||
activeCategory.value = id;
|
activeCategory.value = id;
|
||||||
|
|
||||||
|
// 加载新Tab对应的药品数据
|
||||||
|
currentDrugs.value = JSON.parse(
|
||||||
|
localStorage.getItem(getStorageKey(activePatient.value?.id, id)) || '[]',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -887,7 +1043,7 @@ const selectChineseId = ref(0);
|
|||||||
*/
|
*/
|
||||||
function selectOldDrugInfo(id) {
|
function selectOldDrugInfo(id) {
|
||||||
const check = JSON.parse(
|
const check = JSON.parse(
|
||||||
localStorage.getItem(`prescriptionData${activePatient.value?.id}`) || '[]',
|
localStorage.getItem(getStorageKey(activePatient.value?.id, activeCategory.value)) || '[]',
|
||||||
).find((v) => v.id === id);
|
).find((v) => v.id === id);
|
||||||
if (check) {
|
if (check) {
|
||||||
currentDrugs.value[selectChineseIndex.value].id = selectChineseId.value;
|
currentDrugs.value[selectChineseIndex.value].id = selectChineseId.value;
|
||||||
@@ -1392,9 +1548,17 @@ watch(
|
|||||||
>
|
>
|
||||||
添加商品
|
添加商品
|
||||||
</Button>
|
</Button>
|
||||||
|
<!-- 常用方按钮 -->
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:class="activeCategory !== 1 ? 'ml-3' : ''"
|
||||||
|
@click="openCommonPrescriptionModal"
|
||||||
|
>
|
||||||
|
选择常用方
|
||||||
|
</Button>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
v-model:value="category"
|
v-model:value="category"
|
||||||
:class="activeCategory !== 1 ? 'ml-5' : ''"
|
class="ml-5"
|
||||||
>
|
>
|
||||||
<RadioButton :value="1">自费</RadioButton>
|
<RadioButton :value="1">自费</RadioButton>
|
||||||
<RadioButton :value="2">医保</RadioButton>
|
<RadioButton :value="2">医保</RadioButton>
|
||||||
@@ -1750,11 +1914,62 @@ watch(
|
|||||||
<p>
|
<p>
|
||||||
<span>药品名称:{{ drug.drug_name }}</span>
|
<span>药品名称:{{ drug.drug_name }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<!-- 西药用法显示/编辑模式 -->
|
||||||
|
<p v-if="activeCategory !== 1 && !drug.isEditing">
|
||||||
<span>用法:{{
|
<span>用法:{{
|
||||||
`${drug.use_type?.name},${drug.use_frequency?.name},${drug.use_num?.name},每次${drug.number}${drug.unit?.name}`
|
`${drug.use_type?.name},${drug.use_frequency?.name},${drug.use_num?.name},每次${drug.number}${drug.unit?.name}`
|
||||||
}}</span>
|
}}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<div v-else-if="activeCategory !== 1 && drug.isEditing" class="edit-usage-form">
|
||||||
|
<span>用法:</span>
|
||||||
|
<Select
|
||||||
|
:value="drug.use_type?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 80px"
|
||||||
|
@change="(val) => updateDrugUsage(index, 'use_type', drugUseType.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in drugUseType" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
:value="drug.use_frequency?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 100px; margin-left: 4px"
|
||||||
|
@change="(val) => updateDrugUsage(index, 'use_frequency', drugUseFrequency.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in drugUseFrequency" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
:value="drug.use_num?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 80px; margin-left: 4px"
|
||||||
|
@change="(val) => updateDrugUsage(index, 'use_num', drugTime.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in drugTime" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<span style="margin-left: 4px">每次</span>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="drug.number"
|
||||||
|
:min="1"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px; margin-left: 4px"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
:value="drug.unit?.id"
|
||||||
|
size="small"
|
||||||
|
style="width: 60px; margin-left: 4px"
|
||||||
|
@change="(val) => updateDrugUsage(index, 'unit', drugUnit.find(item => item.id === val))"
|
||||||
|
>
|
||||||
|
<SelectOption v-for="item in drugUnit" :key="item.id" :value="item.id">
|
||||||
|
{{ item.name }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="activeCategory === 1" class="quantity-control">
|
<div v-if="activeCategory === 1" class="quantity-control">
|
||||||
@@ -1774,6 +1989,17 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
<span>{{ drug.price }}</span>
|
<span>{{ drug.price }}</span>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- 西药编辑/保存按钮 -->
|
||||||
|
<Button
|
||||||
|
v-if="activeCategory !== 1 && !drug.isEditing"
|
||||||
|
type="link"
|
||||||
|
@click="toggleEditDrug(index)"
|
||||||
|
>编辑</Button>
|
||||||
|
<Button
|
||||||
|
v-if="activeCategory !== 1 && drug.isEditing"
|
||||||
|
type="link"
|
||||||
|
@click="saveEditDrug(index)"
|
||||||
|
>保存</Button>
|
||||||
<Button type="link" @click="removeDrug(index)">删除</Button>
|
<Button type="link" @click="removeDrug(index)">删除</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="drug.instruction !== ''"
|
v-if="drug.instruction !== ''"
|
||||||
@@ -1929,6 +2155,8 @@ watch(
|
|||||||
<DoctorOrderModals/>
|
<DoctorOrderModals/>
|
||||||
<WesternDrugModal/>
|
<WesternDrugModal/>
|
||||||
<PrescriptionDetailModal/>
|
<PrescriptionDetailModal/>
|
||||||
|
<!-- 常用方选择弹窗 -->
|
||||||
|
<CommonPrescriptionModals/>
|
||||||
<Modal v-model:open="showNewDrugModal" @ok="newDrugModalOk">
|
<Modal v-model:open="showNewDrugModal" @ok="newDrugModalOk">
|
||||||
要把【{{ newDrugInfo.name }}】添加到清单吗?
|
要把【{{ newDrugInfo.name }}】添加到清单吗?
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
272
apps/web-antd/src/views/doctor/patient-management/api/index.ts
Normal file
272
apps/web-antd/src/views/doctor/patient-management/api/index.ts
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
/**
|
||||||
|
* 患者管理相关API
|
||||||
|
*
|
||||||
|
* @description 患者管理模块的所有接口定义
|
||||||
|
* - 患者列表查询(到店/线上)
|
||||||
|
* - 患者详情查看
|
||||||
|
* - 挂号记录和处方记录查询
|
||||||
|
* - 患者备注管理
|
||||||
|
* - 群发消息功能
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const prefix = 'patient-management/';
|
||||||
|
|
||||||
|
// ==================== 类型定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者列表项
|
||||||
|
*/
|
||||||
|
export interface PatientItem {
|
||||||
|
/** 患者ID */
|
||||||
|
id: number;
|
||||||
|
/** 就诊人ID */
|
||||||
|
patient_id: number;
|
||||||
|
/** 患者姓名 */
|
||||||
|
patient: string;
|
||||||
|
/** 头像URL */
|
||||||
|
avatar: string;
|
||||||
|
/** 年龄 */
|
||||||
|
age: number;
|
||||||
|
/** 性别:1-男,2-女 */
|
||||||
|
sex: number;
|
||||||
|
/** 手机号 */
|
||||||
|
mobile: string;
|
||||||
|
/** 最近接诊时间 */
|
||||||
|
recent_accept: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页响应数据结构
|
||||||
|
*/
|
||||||
|
export interface PageResponse<T> {
|
||||||
|
/** 当前页 */
|
||||||
|
page: number;
|
||||||
|
/** 每页数量 */
|
||||||
|
size: number;
|
||||||
|
/** 总页数 */
|
||||||
|
page_count: number;
|
||||||
|
/** 总数量 */
|
||||||
|
total: number;
|
||||||
|
/** 数据列表 */
|
||||||
|
items: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者详情
|
||||||
|
*/
|
||||||
|
export interface PatientDetail {
|
||||||
|
/** 患者基本信息 */
|
||||||
|
patient: {
|
||||||
|
id: number;
|
||||||
|
user_id: number;
|
||||||
|
name: string;
|
||||||
|
sex: number;
|
||||||
|
id_card: string;
|
||||||
|
mobile: string;
|
||||||
|
};
|
||||||
|
/** 头像 */
|
||||||
|
avatar: string;
|
||||||
|
/** 年龄 */
|
||||||
|
age: number;
|
||||||
|
/** 备注 */
|
||||||
|
remark: string;
|
||||||
|
/** 分组 */
|
||||||
|
group: { id: number; name: string }[] | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂号记录项
|
||||||
|
*/
|
||||||
|
export interface RegisterItem {
|
||||||
|
/** 挂号ID */
|
||||||
|
id: number;
|
||||||
|
/** 订单编号 */
|
||||||
|
order_no: string;
|
||||||
|
/** 挂号序号 */
|
||||||
|
order_number: string;
|
||||||
|
/** 挂号费用 */
|
||||||
|
price: number;
|
||||||
|
/** 是否支付 */
|
||||||
|
is_pay: number;
|
||||||
|
/** 状态 */
|
||||||
|
status: number;
|
||||||
|
/** 状态文字 */
|
||||||
|
status_text: string;
|
||||||
|
/** 状态类型 */
|
||||||
|
status_type: string;
|
||||||
|
/** 医生 */
|
||||||
|
doctor: string;
|
||||||
|
/** 诊所 */
|
||||||
|
store: string;
|
||||||
|
/** 创建时间 */
|
||||||
|
created_at: string;
|
||||||
|
/** 支付时间 */
|
||||||
|
pay_time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方记录项
|
||||||
|
*/
|
||||||
|
export interface PrescriptionItem {
|
||||||
|
/** 处方ID */
|
||||||
|
id: number;
|
||||||
|
/** 处方编号 */
|
||||||
|
prescription_no: string;
|
||||||
|
/** 处方类型 */
|
||||||
|
prescription_type: number;
|
||||||
|
/** 类型 */
|
||||||
|
type: number;
|
||||||
|
/** 状态 */
|
||||||
|
status: number;
|
||||||
|
/** 临床诊断 */
|
||||||
|
clinical_diagnose: string;
|
||||||
|
/** 患者信息 */
|
||||||
|
patient: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
sex: number;
|
||||||
|
age: number;
|
||||||
|
};
|
||||||
|
/** 创建时间 */
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== API 函数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取患者列表
|
||||||
|
* @description 获取当前医生名下的患者列表
|
||||||
|
* @param params 查询参数
|
||||||
|
* - status: 1-到店接诊,2-线上接诊(可选)
|
||||||
|
* - name: 患者姓名搜索(可选)
|
||||||
|
* - sex: 性别筛选(可选)
|
||||||
|
* - pageSize: 每页数量(可选)
|
||||||
|
*/
|
||||||
|
export async function getPatientListApi(params?: {
|
||||||
|
status?: number;
|
||||||
|
name?: string;
|
||||||
|
sex?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.get<PageResponse<PatientItem>>(`${prefix}list`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取医生名下所有患者列表
|
||||||
|
* @description 获取当前医生名下的所有患者(不区分到店/线上)
|
||||||
|
* @param keyword 搜索关键字(可选)
|
||||||
|
*/
|
||||||
|
export async function getPatientListsApi(keyword?: string) {
|
||||||
|
return requestClient.get<PageResponse<PatientItem>>(`${prefix}patient-lists`, {
|
||||||
|
params: { keyword },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取患者详情
|
||||||
|
* @description 获取患者的详细信息,包括基本信息、备注、分组
|
||||||
|
* @param upId 就诊人ID
|
||||||
|
*/
|
||||||
|
export async function getPatientDetailApi(upId: number) {
|
||||||
|
return requestClient.get<PatientDetail>(`${prefix}detail`, {
|
||||||
|
params: { up_id: upId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取患者挂号记录列表
|
||||||
|
* @description 获取指定患者的挂号记录
|
||||||
|
* @param upId 就诊人ID
|
||||||
|
* @param pageSize 每页数量(可选)
|
||||||
|
*/
|
||||||
|
export async function getPatientRegisterListApi(
|
||||||
|
upId: number,
|
||||||
|
pageSize?: number,
|
||||||
|
) {
|
||||||
|
return requestClient.get<PageResponse<RegisterItem>>(
|
||||||
|
`${prefix}register-list`,
|
||||||
|
{
|
||||||
|
params: { up_id: upId, pageSize },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取患者处方记录列表
|
||||||
|
* @description 获取指定患者的处方记录
|
||||||
|
* @param upId 就诊人ID
|
||||||
|
* @param pageSize 每页数量(可选)
|
||||||
|
*/
|
||||||
|
export async function getPatientPrescriptionListApi(
|
||||||
|
upId: number,
|
||||||
|
pageSize?: number,
|
||||||
|
) {
|
||||||
|
return requestClient.get<PageResponse<PrescriptionItem>>(
|
||||||
|
`${prefix}prescription-list`,
|
||||||
|
{
|
||||||
|
params: { up_id: upId, pageSize },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加患者备注
|
||||||
|
* @description 为指定患者添加备注
|
||||||
|
* @param upId 就诊人ID
|
||||||
|
* @param remark 备注内容
|
||||||
|
*/
|
||||||
|
export async function savePatientRemarkApi(upId: number, remark: string) {
|
||||||
|
return requestClient.post<any>(`${prefix}save-remark`, {
|
||||||
|
up_id: upId,
|
||||||
|
remark,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新患者备注
|
||||||
|
* @description 更新指定患者的备注内容
|
||||||
|
* @param upId 就诊人ID
|
||||||
|
* @param remark 备注内容
|
||||||
|
*/
|
||||||
|
export async function updatePatientRemarkApi(upId: number, remark: string) {
|
||||||
|
return requestClient.post<any>(`${prefix}update-remark`, {
|
||||||
|
up_id: upId,
|
||||||
|
remark,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群发消息给患者
|
||||||
|
* @description 向多个患者发送消息
|
||||||
|
* @param content 消息内容
|
||||||
|
* @param upIds 患者ID列表(逗号分隔)
|
||||||
|
* @param sendAt 发送时间(可选,留空则立即发送)
|
||||||
|
*/
|
||||||
|
export async function sendMessageApi(
|
||||||
|
content: string,
|
||||||
|
upIds: string,
|
||||||
|
sendAt?: string,
|
||||||
|
) {
|
||||||
|
return requestClient.post<any>(`${prefix}send-message`, {
|
||||||
|
content,
|
||||||
|
up_ids: upIds,
|
||||||
|
send_at: sendAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取患者基础健康信息
|
||||||
|
* @description 获取患者填写的基础健康问卷信息
|
||||||
|
* @param upId 就诊人ID
|
||||||
|
*/
|
||||||
|
export async function getPatientBaseInfoApi(upId: number) {
|
||||||
|
return requestClient.get<any>(`${prefix}base-info`, {
|
||||||
|
params: { up_id: upId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 患者详情弹窗组件
|
||||||
|
*
|
||||||
|
* @description 展示患者的详细信息
|
||||||
|
* - 基本信息、备注、分组
|
||||||
|
* - 挂号记录列表
|
||||||
|
* - 处方记录列表
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Descriptions,
|
||||||
|
Empty,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
TabPane,
|
||||||
|
Tag,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import { UserOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getPatientDetailApi,
|
||||||
|
getPatientRegisterListApi,
|
||||||
|
getPatientPrescriptionListApi,
|
||||||
|
savePatientRemarkApi,
|
||||||
|
updatePatientRemarkApi,
|
||||||
|
type PatientDetail,
|
||||||
|
type RegisterItem,
|
||||||
|
type PrescriptionItem,
|
||||||
|
} from '../api';
|
||||||
|
|
||||||
|
// ==================== 响应式数据 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者ID
|
||||||
|
*/
|
||||||
|
const patientId = ref(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者姓名
|
||||||
|
*/
|
||||||
|
const patientName = ref('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者详情数据
|
||||||
|
*/
|
||||||
|
const patientDetail = ref<PatientDetail | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂号记录列表
|
||||||
|
*/
|
||||||
|
const registerList = ref<RegisterItem[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方记录列表
|
||||||
|
*/
|
||||||
|
const prescriptionList = ref<PrescriptionItem[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载状态
|
||||||
|
*/
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂号记录加载状态
|
||||||
|
*/
|
||||||
|
const registerLoading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方记录加载状态
|
||||||
|
*/
|
||||||
|
const prescriptionLoading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前激活的Tab
|
||||||
|
*/
|
||||||
|
const activeTab = ref('info');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注编辑模式
|
||||||
|
*/
|
||||||
|
const remarkEditing = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注内容
|
||||||
|
*/
|
||||||
|
const remarkContent = ref('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已有备注
|
||||||
|
*/
|
||||||
|
const hasRemark = ref(false);
|
||||||
|
|
||||||
|
// ==================== 表格列配置 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂号记录表格列
|
||||||
|
*/
|
||||||
|
const registerColumns = [
|
||||||
|
{ title: '订单编号', dataIndex: 'order_no', key: 'order_no' },
|
||||||
|
{ title: '医生', dataIndex: 'doctor', key: 'doctor' },
|
||||||
|
{ title: '诊所', dataIndex: 'store', key: 'store' },
|
||||||
|
{ title: '费用', dataIndex: 'price', key: 'price' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status_text',
|
||||||
|
key: 'status_text',
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方记录表格列
|
||||||
|
*/
|
||||||
|
const prescriptionColumns = [
|
||||||
|
{ title: '处方编号', dataIndex: 'prescription_no', key: 'prescription_no' },
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'prescription_type',
|
||||||
|
key: 'prescription_type',
|
||||||
|
customRender: ({ text }: { text: number }) => {
|
||||||
|
const typeMap: Record<number, string> = {
|
||||||
|
1: '中药处方',
|
||||||
|
2: '西药处方',
|
||||||
|
5: '服务包',
|
||||||
|
};
|
||||||
|
return typeMap[text] || '未知';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '诊断', dataIndex: 'clinical_diagnose', key: 'clinical_diagnose' },
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
customRender: ({ text }: { text: number }) => {
|
||||||
|
const statusMap: Record<number, string> = {
|
||||||
|
0: '待审核',
|
||||||
|
1: '已通过',
|
||||||
|
2: '未通过',
|
||||||
|
3: '无需审核',
|
||||||
|
};
|
||||||
|
return statusMap[text] || '未知';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ==================== Modal 配置 ====================
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
footer: false,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<{
|
||||||
|
patientId: number;
|
||||||
|
patientName: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
patientId.value = data.patientId;
|
||||||
|
patientName.value = data.patientName;
|
||||||
|
activeTab.value = 'info';
|
||||||
|
loadPatientDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 方法定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载患者详情
|
||||||
|
*/
|
||||||
|
async function loadPatientDetail() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPatientDetailApi(patientId.value);
|
||||||
|
patientDetail.value = res;
|
||||||
|
remarkContent.value = res.remark || '';
|
||||||
|
hasRemark.value = !!res.remark;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取患者详情失败:', error);
|
||||||
|
message.error('获取患者详情失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载挂号记录
|
||||||
|
*/
|
||||||
|
async function loadRegisterList() {
|
||||||
|
registerLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPatientRegisterListApi(patientId.value);
|
||||||
|
registerList.value = res.items || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取挂号记录失败:', error);
|
||||||
|
message.error('获取挂号记录失败');
|
||||||
|
} finally {
|
||||||
|
registerLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载处方记录
|
||||||
|
*/
|
||||||
|
async function loadPrescriptionList() {
|
||||||
|
prescriptionLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPatientPrescriptionListApi(patientId.value);
|
||||||
|
prescriptionList.value = res.items || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取处方记录失败:', error);
|
||||||
|
message.error('获取处方记录失败');
|
||||||
|
} finally {
|
||||||
|
prescriptionLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab切换处理
|
||||||
|
* @param key Tab键值
|
||||||
|
*/
|
||||||
|
function handleTabChange(key: string) {
|
||||||
|
activeTab.value = key;
|
||||||
|
if (key === 'register' && registerList.value.length === 0) {
|
||||||
|
loadRegisterList();
|
||||||
|
} else if (key === 'prescription' && prescriptionList.value.length === 0) {
|
||||||
|
loadPrescriptionList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存备注
|
||||||
|
*/
|
||||||
|
async function saveRemark() {
|
||||||
|
if (!remarkContent.value.trim()) {
|
||||||
|
message.warning('请输入备注内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (hasRemark.value) {
|
||||||
|
await updatePatientRemarkApi(patientId.value, remarkContent.value);
|
||||||
|
} else {
|
||||||
|
await savePatientRemarkApi(patientId.value, remarkContent.value);
|
||||||
|
}
|
||||||
|
message.success('备注保存成功');
|
||||||
|
remarkEditing.value = false;
|
||||||
|
hasRemark.value = true;
|
||||||
|
// 刷新详情
|
||||||
|
loadPatientDetail();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存备注失败:', error);
|
||||||
|
message.error('保存备注失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取性别文字
|
||||||
|
* @param sex 性别值
|
||||||
|
*/
|
||||||
|
function getSexText(sex: number): string {
|
||||||
|
return sex === 1 ? '男' : sex === 2 ? '女' : '未知';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[70%]" :title="`患者详情 - ${patientName}`">
|
||||||
|
<Page>
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<Tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<TabPane key="info" tab="基本信息">
|
||||||
|
<div v-if="patientDetail">
|
||||||
|
<Row :gutter="24">
|
||||||
|
<!-- 左侧:头像和基本信息 -->
|
||||||
|
<Col :span="8">
|
||||||
|
<Card>
|
||||||
|
<div class="text-center">
|
||||||
|
<Avatar :size="100" :src="patientDetail.avatar">
|
||||||
|
<template #icon><UserOutlined /></template>
|
||||||
|
</Avatar>
|
||||||
|
<h3 class="mt-4 text-xl font-bold">
|
||||||
|
{{ patientDetail.patient?.name }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2">
|
||||||
|
<Tag
|
||||||
|
:color="
|
||||||
|
patientDetail.patient?.sex === 1
|
||||||
|
? '#1890ff'
|
||||||
|
: '#eb2f96'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ getSexText(patientDetail.patient?.sex) }}
|
||||||
|
</Tag>
|
||||||
|
<Tag color="blue">{{ patientDetail.age }}岁</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<!-- 右侧:详细信息 -->
|
||||||
|
<Col :span="16">
|
||||||
|
<Card title="患者信息">
|
||||||
|
<Descriptions :column="2" bordered>
|
||||||
|
<Descriptions.Item label="姓名">
|
||||||
|
{{ patientDetail.patient?.name }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="性别">
|
||||||
|
{{ getSexText(patientDetail.patient?.sex) }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="年龄">
|
||||||
|
{{ patientDetail.age }}岁
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="手机号">
|
||||||
|
{{ patientDetail.patient?.mobile || '未填写' }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="身份证" :span="2">
|
||||||
|
{{ patientDetail.patient?.id_card || '未填写' }}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- 分组信息 -->
|
||||||
|
<Card title="分组信息" class="mt-4">
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
Array.isArray(patientDetail.group) &&
|
||||||
|
patientDetail.group.length > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
v-for="item in patientDetail.group"
|
||||||
|
:key="item.id"
|
||||||
|
color="blue"
|
||||||
|
class="mr-2 mb-2"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
<Empty v-else description="暂无分组" />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- 备注信息 -->
|
||||||
|
<Card title="备注" class="mt-4">
|
||||||
|
<div v-if="!remarkEditing">
|
||||||
|
<p v-if="patientDetail.remark">
|
||||||
|
{{ patientDetail.remark }}
|
||||||
|
</p>
|
||||||
|
<Empty v-else description="暂无备注" />
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="mt-2"
|
||||||
|
@click="remarkEditing = true"
|
||||||
|
>
|
||||||
|
{{ hasRemark ? '编辑备注' : '添加备注' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<Input.TextArea
|
||||||
|
v-model:value="remarkContent"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="请输入备注内容"
|
||||||
|
/>
|
||||||
|
<div class="mt-2">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="saveRemark"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
class="ml-2"
|
||||||
|
@click="remarkEditing = false"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
<Empty v-else description="暂无数据" />
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<!-- 挂号记录 -->
|
||||||
|
<TabPane key="register" tab="挂号记录">
|
||||||
|
<Spin :spinning="registerLoading">
|
||||||
|
<Table
|
||||||
|
:columns="registerColumns"
|
||||||
|
:data-source="registerList"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="id"
|
||||||
|
>
|
||||||
|
<template #emptyText>
|
||||||
|
<Empty description="暂无挂号记录" />
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Spin>
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<!-- 处方记录 -->
|
||||||
|
<TabPane key="prescription" tab="处方记录">
|
||||||
|
<Spin :spinning="prescriptionLoading">
|
||||||
|
<Table
|
||||||
|
:columns="prescriptionColumns"
|
||||||
|
:data-source="prescriptionList"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="id"
|
||||||
|
>
|
||||||
|
<template #emptyText>
|
||||||
|
<Empty description="暂无处方记录" />
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</Spin>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Spin>
|
||||||
|
</Page>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.ant-descriptions-item-label) {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 群发消息弹窗组件
|
||||||
|
*
|
||||||
|
* @description 向多个患者群发消息
|
||||||
|
* - 支持编辑消息内容
|
||||||
|
* - 支持定时发送
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DatePicker,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Switch,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { sendMessageApi } from '../api';
|
||||||
|
|
||||||
|
// ==================== 响应式数据 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者ID列表
|
||||||
|
*/
|
||||||
|
const patientIds = ref<number[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
const messageContent = ref('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否定时发送
|
||||||
|
*/
|
||||||
|
const isScheduled = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时发送时间
|
||||||
|
*/
|
||||||
|
const scheduledTime = ref<dayjs.Dayjs | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送中状态
|
||||||
|
*/
|
||||||
|
const sending = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功回调函数
|
||||||
|
*/
|
||||||
|
let onSuccessCallback: (() => void) | null = null;
|
||||||
|
|
||||||
|
// ==================== Modal 配置 ====================
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
onCancel() {
|
||||||
|
resetForm();
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
await handleSend();
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<{
|
||||||
|
patientIds: number[];
|
||||||
|
onSuccess?: () => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
patientIds.value = data.patientIds;
|
||||||
|
if (data.onSuccess) {
|
||||||
|
onSuccessCallback = data.onSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 方法定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
*/
|
||||||
|
async function handleSend() {
|
||||||
|
// 校验
|
||||||
|
if (!messageContent.value.trim()) {
|
||||||
|
message.warning('请输入消息内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patientIds.value.length === 0) {
|
||||||
|
message.warning('没有选择发送对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isScheduled.value && !scheduledTime.value) {
|
||||||
|
message.warning('请选择定时发送时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sending.value = true;
|
||||||
|
modalApi.setState({ confirmLoading: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 转换患者ID为逗号分隔的字符串
|
||||||
|
const upIds = patientIds.value.join(',');
|
||||||
|
|
||||||
|
// 定时发送时间格式化
|
||||||
|
const sendAt = isScheduled.value && scheduledTime.value
|
||||||
|
? scheduledTime.value.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
await sendMessageApi(messageContent.value, upIds, sendAt);
|
||||||
|
|
||||||
|
message.success(
|
||||||
|
isScheduled.value
|
||||||
|
? `消息已设置定时发送,将于 ${sendAt} 发送给 ${patientIds.value.length} 位患者`
|
||||||
|
: `消息已发送给 ${patientIds.value.length} 位患者`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 调用成功回调
|
||||||
|
if (onSuccessCallback) {
|
||||||
|
onSuccessCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
modalApi.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送消息失败:', error);
|
||||||
|
message.error('发送消息失败,请重试');
|
||||||
|
} finally {
|
||||||
|
sending.value = false;
|
||||||
|
modalApi.setState({ confirmLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置表单
|
||||||
|
*/
|
||||||
|
function resetForm() {
|
||||||
|
messageContent.value = '';
|
||||||
|
isScheduled.value = false;
|
||||||
|
scheduledTime.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用过去的时间
|
||||||
|
* @param current 当前日期
|
||||||
|
*/
|
||||||
|
function disabledDate(current: dayjs.Dayjs) {
|
||||||
|
return current && current < dayjs().startOf('day');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用过去的时间(分钟级别)
|
||||||
|
*/
|
||||||
|
function disabledTime() {
|
||||||
|
const now = dayjs();
|
||||||
|
return {
|
||||||
|
disabledHours: () => {
|
||||||
|
const hours = [];
|
||||||
|
for (let i = 0; i < now.hour(); i++) {
|
||||||
|
hours.push(i);
|
||||||
|
}
|
||||||
|
return hours;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[500px]" title="群发消息">
|
||||||
|
<Page>
|
||||||
|
<Form layout="vertical">
|
||||||
|
<!-- 发送对象提示 -->
|
||||||
|
<FormItem>
|
||||||
|
<div class="send-info">
|
||||||
|
将向 <span class="count">{{ patientIds.length }}</span> 位患者发送消息
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<!-- 消息内容 -->
|
||||||
|
<FormItem label="消息内容" required>
|
||||||
|
<Input.TextArea
|
||||||
|
v-model:value="messageContent"
|
||||||
|
:rows="6"
|
||||||
|
:maxlength="500"
|
||||||
|
show-count
|
||||||
|
placeholder="请输入要发送的消息内容..."
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<!-- 定时发送开关 -->
|
||||||
|
<FormItem label="定时发送">
|
||||||
|
<Switch v-model:checked="isScheduled" />
|
||||||
|
<span class="ml-2 text-gray-500">
|
||||||
|
{{ isScheduled ? '开启定时发送' : '立即发送' }}
|
||||||
|
</span>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<!-- 定时发送时间 -->
|
||||||
|
<FormItem v-if="isScheduled" label="发送时间" required>
|
||||||
|
<DatePicker
|
||||||
|
v-model:value="scheduledTime"
|
||||||
|
show-time
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="请选择发送时间"
|
||||||
|
:disabled-date="disabledDate"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<FormItem>
|
||||||
|
<div class="tips">
|
||||||
|
<p>提示:</p>
|
||||||
|
<ul>
|
||||||
|
<li>消息将通过微信订阅消息发送给患者</li>
|
||||||
|
<li>请确保消息内容符合平台规范</li>
|
||||||
|
<li v-if="isScheduled">定时消息将在指定时间自动发送</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Page>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.send-info {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #e6f7ff;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
background: rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #fffbe6;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ad8b00;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
background: rgba(250, 173, 20, 0.1);
|
||||||
|
color: #d4b106;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
455
apps/web-antd/src/views/doctor/patient-management/index.vue
Normal file
455
apps/web-antd/src/views/doctor/patient-management/index.vue
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 患者管理页面
|
||||||
|
*
|
||||||
|
* @description 医生名下患者管理功能
|
||||||
|
* - 患者列表展示
|
||||||
|
* - 搜索筛选功能
|
||||||
|
* - 查看患者详情
|
||||||
|
* - 群发消息功能
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Empty,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Pagination,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
Spin,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
ManOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
SendOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
WomanOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getPatientListApi,
|
||||||
|
type PatientItem,
|
||||||
|
type PageResponse,
|
||||||
|
} from './api';
|
||||||
|
import PatientDetailModal from './components/PatientDetailModal.vue';
|
||||||
|
import SendMessageModal from './components/SendMessageModal.vue';
|
||||||
|
|
||||||
|
// ==================== 响应式数据 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者列表数据
|
||||||
|
*/
|
||||||
|
const patientList = ref<PatientItem[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页信息
|
||||||
|
*/
|
||||||
|
const pagination = ref({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载状态
|
||||||
|
*/
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索关键字
|
||||||
|
*/
|
||||||
|
const searchName = ref('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别筛选
|
||||||
|
*/
|
||||||
|
const searchSex = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中的患者ID列表(用于群发消息)
|
||||||
|
*/
|
||||||
|
const selectedPatientIds = ref<number[]>([]);
|
||||||
|
|
||||||
|
// ==================== Modal 配置 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者详情弹窗
|
||||||
|
*/
|
||||||
|
const [PatientDetailModals, PatientDetailModalApi] = useVbenModal({
|
||||||
|
connectedComponent: PatientDetailModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群发消息弹窗
|
||||||
|
*/
|
||||||
|
const [SendMessageModals, SendMessageModalApi] = useVbenModal({
|
||||||
|
connectedComponent: SendMessageModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 方法定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取患者列表
|
||||||
|
* @description 根据搜索条件获取患者列表
|
||||||
|
*/
|
||||||
|
async function fetchPatientList() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getPatientListApi({
|
||||||
|
name: searchName.value || undefined,
|
||||||
|
sex: searchSex.value,
|
||||||
|
pageSize: pagination.value.pageSize,
|
||||||
|
});
|
||||||
|
patientList.value = res.items || [];
|
||||||
|
pagination.value.total = res.total || 0;
|
||||||
|
pagination.value.current = res.page || 1;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取患者列表失败:', error);
|
||||||
|
message.error('获取患者列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索患者
|
||||||
|
* @description 触发搜索
|
||||||
|
*/
|
||||||
|
function handleSearch() {
|
||||||
|
pagination.value.current = 1;
|
||||||
|
fetchPatientList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置搜索条件
|
||||||
|
*/
|
||||||
|
function handleReset() {
|
||||||
|
searchName.value = '';
|
||||||
|
searchSex.value = undefined;
|
||||||
|
pagination.value.current = 1;
|
||||||
|
fetchPatientList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页变化
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
*/
|
||||||
|
function handlePageChange(page: number, pageSize: number) {
|
||||||
|
pagination.value.current = page;
|
||||||
|
pagination.value.pageSize = pageSize;
|
||||||
|
fetchPatientList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开患者详情弹窗
|
||||||
|
* @param patient 患者信息
|
||||||
|
*/
|
||||||
|
function openPatientDetail(patient: PatientItem) {
|
||||||
|
PatientDetailModalApi.setData({
|
||||||
|
patientId: patient.patient_id,
|
||||||
|
patientName: patient.patient,
|
||||||
|
});
|
||||||
|
PatientDetailModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开群发消息弹窗
|
||||||
|
*/
|
||||||
|
function openSendMessageModal() {
|
||||||
|
if (selectedPatientIds.value.length === 0) {
|
||||||
|
message.warning('请先选择要发送消息的患者');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SendMessageModalApi.setData({
|
||||||
|
patientIds: selectedPatientIds.value,
|
||||||
|
onSuccess: () => {
|
||||||
|
selectedPatientIds.value = [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
SendMessageModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择/取消选择患者
|
||||||
|
* @param patientId 患者ID
|
||||||
|
*/
|
||||||
|
function toggleSelectPatient(patientId: number) {
|
||||||
|
const index = selectedPatientIds.value.indexOf(patientId);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedPatientIds.value.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
selectedPatientIds.value.push(patientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全选/取消全选
|
||||||
|
*/
|
||||||
|
function toggleSelectAll() {
|
||||||
|
if (selectedPatientIds.value.length === patientList.value.length) {
|
||||||
|
selectedPatientIds.value = [];
|
||||||
|
} else {
|
||||||
|
selectedPatientIds.value = patientList.value.map((p) => p.patient_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取性别文字
|
||||||
|
* @param sex 性别值
|
||||||
|
*/
|
||||||
|
function getSexText(sex: number): string {
|
||||||
|
return sex === 1 ? '男' : sex === 2 ? '女' : '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取性别颜色
|
||||||
|
* @param sex 性别值
|
||||||
|
*/
|
||||||
|
function getSexColor(sex: number): string {
|
||||||
|
return sex === 1 ? '#1890ff' : sex === 2 ? '#eb2f96' : '#999';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 生命周期 ====================
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchPatientList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page title="患者管理" auto-content-height>
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<Card class="mb-4">
|
||||||
|
<Row :gutter="16" align="middle">
|
||||||
|
<Col :span="6">
|
||||||
|
<Input
|
||||||
|
v-model:value="searchName"
|
||||||
|
placeholder="请输入患者姓名"
|
||||||
|
allow-clear
|
||||||
|
@pressEnter="handleSearch"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<SearchOutlined />
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
</Col>
|
||||||
|
<Col :span="4">
|
||||||
|
<Select
|
||||||
|
v-model:value="searchSex"
|
||||||
|
placeholder="选择性别"
|
||||||
|
allow-clear
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<SelectOption :value="1">男</SelectOption>
|
||||||
|
<SelectOption :value="2">女</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
<Col :span="8">
|
||||||
|
<Button type="primary" @click="handleSearch">搜索</Button>
|
||||||
|
<Button class="ml-2" @click="handleReset">重置</Button>
|
||||||
|
</Col>
|
||||||
|
<Col :span="6" class="text-right">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:disabled="selectedPatientIds.length === 0"
|
||||||
|
@click="openSendMessageModal"
|
||||||
|
>
|
||||||
|
<template #icon><SendOutlined /></template>
|
||||||
|
群发消息 ({{ selectedPatientIds.length }})
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- 患者列表 -->
|
||||||
|
<Card>
|
||||||
|
<Spin :spinning="loading">
|
||||||
|
<div v-if="patientList.length === 0" class="py-12 text-center">
|
||||||
|
<Empty description="暂无患者数据" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<!-- 全选操作 -->
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<Button size="small" @click="toggleSelectAll">
|
||||||
|
{{
|
||||||
|
selectedPatientIds.length === patientList.length
|
||||||
|
? '取消全选'
|
||||||
|
: '全选'
|
||||||
|
}}
|
||||||
|
</Button>
|
||||||
|
<span class="text-gray-500">
|
||||||
|
共 {{ pagination.total }} 位患者
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 患者卡片列表 -->
|
||||||
|
<Row :gutter="[16, 16]">
|
||||||
|
<Col
|
||||||
|
v-for="patient in patientList"
|
||||||
|
:key="patient.patient_id"
|
||||||
|
:xs="24"
|
||||||
|
:sm="12"
|
||||||
|
:md="8"
|
||||||
|
:lg="6"
|
||||||
|
:xl="4"
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
hoverable
|
||||||
|
:class="{
|
||||||
|
'patient-card': true,
|
||||||
|
selected: selectedPatientIds.includes(patient.patient_id),
|
||||||
|
}"
|
||||||
|
@click="toggleSelectPatient(patient.patient_id)"
|
||||||
|
>
|
||||||
|
<div class="patient-card-content">
|
||||||
|
<!-- 头像 -->
|
||||||
|
<Avatar
|
||||||
|
:size="64"
|
||||||
|
:src="patient.avatar"
|
||||||
|
class="patient-avatar"
|
||||||
|
>
|
||||||
|
<template #icon><UserOutlined /></template>
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<div class="patient-info">
|
||||||
|
<h4 class="patient-name">{{ patient.patient }}</h4>
|
||||||
|
<div class="patient-tags">
|
||||||
|
<Tag :color="getSexColor(patient.sex)">
|
||||||
|
<template #icon>
|
||||||
|
<ManOutlined v-if="patient.sex === 1" />
|
||||||
|
<WomanOutlined v-else-if="patient.sex === 2" />
|
||||||
|
</template>
|
||||||
|
{{ getSexText(patient.sex) }}
|
||||||
|
</Tag>
|
||||||
|
<Tag v-if="patient.age" color="blue"
|
||||||
|
>{{ patient.age }}岁</Tag
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="patient-mobile" v-if="patient.mobile">
|
||||||
|
{{ patient.mobile }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="patient-actions" @click.stop>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="openPatientDetail(patient)"
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="mt-4 text-right">
|
||||||
|
<Pagination
|
||||||
|
v-model:current="pagination.current"
|
||||||
|
v-model:pageSize="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
show-size-changer
|
||||||
|
show-quick-jumper
|
||||||
|
@change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<!-- 弹窗组件 -->
|
||||||
|
<PatientDetailModals />
|
||||||
|
<SendMessageModals />
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.patient-card {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
background-color: rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-card-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-avatar {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-info {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-tags {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-mobile {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.dark & {
|
||||||
|
border-top-color: #374151;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
const prefix = 'doctor/';
|
const prefix = 'doctor/';
|
||||||
|
const commonPrescriptionPrefix = 'common-prescription/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前登录的医生信息
|
* 获取当前登录的医生信息
|
||||||
* @param data
|
* @param data
|
||||||
@@ -8,6 +10,284 @@ const prefix = 'doctor/';
|
|||||||
export async function getDoctorMyInfoApi(data: any) {
|
export async function getDoctorMyInfoApi(data: any) {
|
||||||
return requestClient.get<any>(`${prefix}my-info`, { params: data });
|
return requestClient.get<any>(`${prefix}my-info`, { params: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 常用方管理 API ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方列表响应数据结构
|
||||||
|
* @description 包含西药、中药、颗粒药三种类型的常用方
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionResponse {
|
||||||
|
/** 西药常用方列表 */
|
||||||
|
west_prescription: any[];
|
||||||
|
/** 西药详细药品信息 */
|
||||||
|
west: any[][];
|
||||||
|
/** 中药常用方列表 */
|
||||||
|
chin_prescription: any[];
|
||||||
|
/** 中药详细药品信息 */
|
||||||
|
chinese: any[][];
|
||||||
|
/** 颗粒药常用方列表 */
|
||||||
|
granular_prescription: any[];
|
||||||
|
/** 颗粒药详细药品信息 */
|
||||||
|
granular: any[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取常用方列表
|
||||||
|
* @description 获取当前医生的所有常用方(西药/中药/颗粒药)
|
||||||
|
* @param storeId 诊所ID(可选)
|
||||||
|
*/
|
||||||
|
export async function getCommonPrescriptionListApi(storeId?: number) {
|
||||||
|
return requestClient.get<CommonPrescriptionResponse>(
|
||||||
|
`${commonPrescriptionPrefix}list`,
|
||||||
|
{ params: { store_id: storeId } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取常用方详情
|
||||||
|
* @description 根据ID和类型获取常用方的详细信息
|
||||||
|
* @param id 常用方ID
|
||||||
|
* @param type 类型:west-西药,chinese-中药,granular-颗粒药
|
||||||
|
*/
|
||||||
|
export async function getCommonPrescriptionDetailApi(
|
||||||
|
id: number,
|
||||||
|
type: string,
|
||||||
|
) {
|
||||||
|
return requestClient.get<any[]>(`${commonPrescriptionPrefix}detail`, {
|
||||||
|
params: { id, type },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方药品数据结构(西药/中成药)
|
||||||
|
* @description 西药药品的详细信息
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionWestDrug {
|
||||||
|
/** 药品ID */
|
||||||
|
drug_id?: number;
|
||||||
|
/** 药品ID(兼容字段) */
|
||||||
|
id?: number;
|
||||||
|
/** 药品名称 */
|
||||||
|
drug_name: string;
|
||||||
|
/** 每次用量 */
|
||||||
|
number?: number;
|
||||||
|
/** 购买数量 */
|
||||||
|
select_number?: number;
|
||||||
|
/** 单价 */
|
||||||
|
price?: number;
|
||||||
|
/** 使用时间ID */
|
||||||
|
time_id?: number;
|
||||||
|
/** 使用类型ID */
|
||||||
|
type_id?: number;
|
||||||
|
/** 使用频率ID */
|
||||||
|
frequency_id?: number;
|
||||||
|
/** 单位ID */
|
||||||
|
unit_id?: number;
|
||||||
|
/** 药品图片 */
|
||||||
|
image?: string;
|
||||||
|
/** 说明书 */
|
||||||
|
instruction?: string;
|
||||||
|
/** 使用时间信息 */
|
||||||
|
use_num?: { name?: string };
|
||||||
|
/** 使用类型信息 */
|
||||||
|
use_type?: { name?: string };
|
||||||
|
/** 使用频率信息 */
|
||||||
|
use_frequency?: { name?: string };
|
||||||
|
/** 单位信息 */
|
||||||
|
unit?: { name?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方药品数据结构(中药/颗粒药)
|
||||||
|
* @description 中药/颗粒药药品的详细信息
|
||||||
|
*/
|
||||||
|
export interface CommonPrescriptionChineseDrug {
|
||||||
|
/** 药品ID */
|
||||||
|
drug_id?: number;
|
||||||
|
/** 药品ID(兼容字段) */
|
||||||
|
id?: number;
|
||||||
|
/** 药品名称 */
|
||||||
|
drug_name: string;
|
||||||
|
/** 克数/用量 */
|
||||||
|
number?: number;
|
||||||
|
/** 单价 */
|
||||||
|
price?: number;
|
||||||
|
/** 用法ID */
|
||||||
|
way_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存西药常用方
|
||||||
|
* @description 将当前处方保存为西药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
* @param data 常用方数据
|
||||||
|
*/
|
||||||
|
export async function saveWestCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionWestDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(`${commonPrescriptionPrefix}save-west`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存中药常用方
|
||||||
|
* @description 将当前处方保存为中药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
* @param data 常用方数据
|
||||||
|
*/
|
||||||
|
export async function saveChineseCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
/** 剂量/天数(可选,默认7) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选,默认2) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}save-chinese`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存颗粒药常用方
|
||||||
|
* @description 将当前处方保存为颗粒药常用方模板,后端会自动创建 recipe 记录
|
||||||
|
* @param data 常用方数据
|
||||||
|
*/
|
||||||
|
export async function saveGranularCommonPrescriptionApi(data: {
|
||||||
|
/** 常用方名称 */
|
||||||
|
name: string;
|
||||||
|
/** 药品详细信息数组 */
|
||||||
|
drugs: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 诊所ID */
|
||||||
|
store_id: number;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
/** 类别 1-自费 2-医保(可选) */
|
||||||
|
category?: string;
|
||||||
|
/** 剂量/天数(可选,默认7) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选,默认2) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}save-granular`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除常用方
|
||||||
|
* @description 删除指定的常用方
|
||||||
|
* @param id 常用方ID
|
||||||
|
* @param type 类型:west-西药,chinese-中药,granular-颗粒药
|
||||||
|
*/
|
||||||
|
export async function deleteCommonPrescriptionApi(id: number, type: string) {
|
||||||
|
return requestClient.post<any>(`${commonPrescriptionPrefix}delete`, {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方更新参数(基础)
|
||||||
|
* @description 更新常用方基本信息的参数
|
||||||
|
*/
|
||||||
|
export interface UpdateCommonPrescriptionBaseParams {
|
||||||
|
/** 常用方ID */
|
||||||
|
id: number;
|
||||||
|
/** 常用方名称(可选) */
|
||||||
|
name?: string;
|
||||||
|
/** 临床诊断(可选) */
|
||||||
|
clinical_diagnose?: string;
|
||||||
|
/** 医嘱(可选) */
|
||||||
|
doctor_order?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 西药常用方更新参数
|
||||||
|
* @description 更新西药常用方,支持更新药品列表
|
||||||
|
*/
|
||||||
|
export interface UpdateWestCommonPrescriptionParams extends UpdateCommonPrescriptionBaseParams {
|
||||||
|
/** 药品数组(可选) */
|
||||||
|
drugs?: CommonPrescriptionWestDrug[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中药/颗粒药常用方更新参数
|
||||||
|
* @description 更新中药/颗粒药常用方,支持更新药品列表
|
||||||
|
*/
|
||||||
|
export interface UpdateChineseCommonPrescriptionParams extends UpdateCommonPrescriptionBaseParams {
|
||||||
|
/** 药品数组(可选) */
|
||||||
|
drugs?: CommonPrescriptionChineseDrug[];
|
||||||
|
/** 剂量/天数(可选) */
|
||||||
|
dosage?: number;
|
||||||
|
/** 每日次数(可选) */
|
||||||
|
day_dosage?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新西药常用方
|
||||||
|
* @description 更新西药常用方的基本信息和药品列表
|
||||||
|
* @param data 更新参数
|
||||||
|
*/
|
||||||
|
export async function updateWestCommonPrescriptionApi(
|
||||||
|
data: UpdateWestCommonPrescriptionParams,
|
||||||
|
) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}update-west`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新中药常用方
|
||||||
|
* @description 更新中药常用方的基本信息和药品列表
|
||||||
|
* @param data 更新参数
|
||||||
|
*/
|
||||||
|
export async function updateChineseCommonPrescriptionApi(
|
||||||
|
data: UpdateChineseCommonPrescriptionParams,
|
||||||
|
) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}update-chinese`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新颗粒药常用方
|
||||||
|
* @description 更新颗粒药常用方的基本信息和药品列表
|
||||||
|
* @param data 更新参数
|
||||||
|
*/
|
||||||
|
export async function updateGranularCommonPrescriptionApi(
|
||||||
|
data: UpdateChineseCommonPrescriptionParams,
|
||||||
|
) {
|
||||||
|
return requestClient.post<any>(
|
||||||
|
`${commonPrescriptionPrefix}update-granular`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 获取当前登录的医生信息
|
* 获取当前登录的医生信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,33 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 医生设置页面
|
||||||
|
*
|
||||||
|
* @description 医生个人中心,包含个人资料、常用方、常用医嘱、常用诊断等功能
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { EllipsisText, Page } from '@vben/common-ui';
|
import { EllipsisText, Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
|
// 常用方编辑弹窗组件
|
||||||
|
import EditCommonPrescriptionModal from './components/EditCommonPrescriptionModal.vue';
|
||||||
|
// 常用方新增弹窗组件
|
||||||
|
import AddCommonPrescriptionModal from './components/AddCommonPrescriptionModal.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
Collapse,
|
||||||
|
CollapsePanel,
|
||||||
|
Empty,
|
||||||
Input,
|
Input,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
Modal,
|
||||||
notification,
|
notification,
|
||||||
|
Popconfirm,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Rate,
|
Rate,
|
||||||
@@ -20,30 +37,37 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
createDoctorOrderApi,
|
createDoctorOrderApi,
|
||||||
|
deleteCommonPrescriptionApi,
|
||||||
|
getCommonPrescriptionListApi,
|
||||||
getDoctorMyDiseaseListApi,
|
getDoctorMyDiseaseListApi,
|
||||||
getDoctorMyDoctorOrderApi,
|
getDoctorMyDoctorOrderApi,
|
||||||
getDoctorMyInfoApi,
|
getDoctorMyInfoApi,
|
||||||
saveServicePriceApi,
|
saveServicePriceApi,
|
||||||
} from '#/views/doctor/settings/api';
|
} from '#/views/doctor/settings/api';
|
||||||
|
import type { CommonPrescriptionResponse } from '#/views/doctor/settings/api';
|
||||||
|
|
||||||
const userInfoStore = useUserStore();
|
const userInfoStore = useUserStore();
|
||||||
|
|
||||||
// 映射数据(示例)
|
// 映射数据(示例)
|
||||||
const identityMap = { 1: '中医', 2: '西医' };
|
const identityMap = { 1: '中医', 2: '西医' };
|
||||||
const activeTabBar = ref(1);
|
const activeTabBar = ref(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab栏配置
|
||||||
|
* - 1: 个人资料
|
||||||
|
* - 3: 常用方(新增)
|
||||||
|
* - 4: 常用医嘱
|
||||||
|
* - 5: 常用诊断
|
||||||
|
*/
|
||||||
const tabBar = [
|
const tabBar = [
|
||||||
{
|
{
|
||||||
value: 1,
|
value: 1,
|
||||||
label: '个人资料',
|
label: '个人资料',
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// value: 2,
|
value: 3,
|
||||||
// label: '我的处方',
|
label: '常用方',
|
||||||
// },
|
},
|
||||||
// {
|
|
||||||
// value: 3,
|
|
||||||
// label: '常用方',
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
value: 4,
|
value: 4,
|
||||||
label: '常用医嘱',
|
label: '常用医嘱',
|
||||||
@@ -98,27 +122,146 @@ const getDoctorMyDiseaseList = () => {
|
|||||||
doctorMyDiseaseList.value = res;
|
doctorMyDiseaseList.value = res;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 常用方相关 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方数据
|
||||||
|
* @description 包含西药、中药、颗粒药三种类型的常用方列表
|
||||||
|
*/
|
||||||
|
const commonPrescriptionData = ref<CommonPrescriptionResponse | null>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用方加载状态
|
||||||
|
*/
|
||||||
|
const commonPrescriptionLoading = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取常用方列表
|
||||||
|
* @description 获取当前医生的所有常用方
|
||||||
|
*/
|
||||||
|
const getCommonPrescriptionList = async () => {
|
||||||
|
commonPrescriptionLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getCommonPrescriptionListApi();
|
||||||
|
commonPrescriptionData.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取常用方列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
commonPrescriptionLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除常用方
|
||||||
|
* @param id 常用方ID
|
||||||
|
* @param type 类型:west-西药,chinese-中药,granular-颗粒药
|
||||||
|
*/
|
||||||
|
const handleDeleteCommonPrescription = async (id: number, type: string) => {
|
||||||
|
try {
|
||||||
|
await deleteCommonPrescriptionApi(id, type);
|
||||||
|
notification.success({
|
||||||
|
message: '删除成功',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
// 刷新列表
|
||||||
|
getCommonPrescriptionList();
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: '删除失败',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取药品名称列表(用于展示)
|
||||||
|
* @param recipes 药品列表
|
||||||
|
* @param nameField 名称字段
|
||||||
|
*/
|
||||||
|
const getDrugNames = (recipes: any[], nameField = 'drug_name') => {
|
||||||
|
if (!recipes || recipes.length === 0) return '暂无药品';
|
||||||
|
return recipes.map((item) => item[nameField] || item.name).join('、');
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 常用方编辑弹窗 ====================
|
||||||
|
|
||||||
|
const [EditCommonPrescriptionModals, EditCommonPrescriptionModalApi] =
|
||||||
|
useVbenModal({
|
||||||
|
connectedComponent: EditCommonPrescriptionModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 常用方新增弹窗 ====================
|
||||||
|
|
||||||
|
const [AddCommonPrescriptionModals, AddCommonPrescriptionModalApi] =
|
||||||
|
useVbenModal({
|
||||||
|
connectedComponent: AddCommonPrescriptionModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开新增常用方弹窗
|
||||||
|
*/
|
||||||
|
const handleAddCommonPrescription = () => {
|
||||||
|
AddCommonPrescriptionModalApi.setData({
|
||||||
|
onSave: () => {
|
||||||
|
// 刷新列表
|
||||||
|
getCommonPrescriptionList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
AddCommonPrescriptionModalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开编辑常用方弹窗
|
||||||
|
* @param prescription 常用方信息
|
||||||
|
* @param recipes 药品列表
|
||||||
|
* @param type 类型:west-西药,chinese-中药,granular-颗粒药
|
||||||
|
*/
|
||||||
|
const handleEditCommonPrescription = (
|
||||||
|
prescription: any,
|
||||||
|
recipes: any[],
|
||||||
|
type: 'west' | 'chinese' | 'granular',
|
||||||
|
) => {
|
||||||
|
EditCommonPrescriptionModalApi.setData({
|
||||||
|
prescription,
|
||||||
|
recipes,
|
||||||
|
type,
|
||||||
|
onSave: () => {
|
||||||
|
// 刷新列表
|
||||||
|
getCommonPrescriptionList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
EditCommonPrescriptionModalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 医嘱列表
|
* 医嘱列表
|
||||||
*/
|
*/
|
||||||
const doctorOrder = ref([]);
|
const doctorOrder = ref([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab切换事件处理
|
||||||
|
* @description 根据当前选中的Tab加载对应的数据
|
||||||
|
*/
|
||||||
const tabBarChange = () => {
|
const tabBarChange = () => {
|
||||||
switch (activeTabBar.value) {
|
switch (activeTabBar.value) {
|
||||||
case 1: {
|
case 1: {
|
||||||
|
// 个人资料
|
||||||
getMyInfo();
|
getMyInfo();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 3: {
|
case 3: {
|
||||||
|
// 常用方
|
||||||
|
getCommonPrescriptionList();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 4: {
|
case 4: {
|
||||||
|
// 常用医嘱
|
||||||
getDoctorMyDoctorOrder();
|
getDoctorMyDoctorOrder();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 5: {
|
case 5: {
|
||||||
|
// 常用诊断
|
||||||
getDoctorMyDiseaseList();
|
getDoctorMyDiseaseList();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -335,7 +478,210 @@ const createDoctorOrder = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card v-else-if="data && activeTabBar === 2" title="我的处方" />
|
<Card v-else-if="data && activeTabBar === 2" title="我的处方" />
|
||||||
<Card v-else-if="data && activeTabBar === 3" title="常用方" />
|
<!-- 常用方Tab内容 -->
|
||||||
|
<Card v-else-if="activeTabBar === 3">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>常用方</span>
|
||||||
|
<Button type="primary" @click="handleAddCommonPrescription">
|
||||||
|
新增常用方
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="commonPrescriptionLoading" class="text-center py-8">
|
||||||
|
加载中...
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!commonPrescriptionData" class="text-center py-8">
|
||||||
|
<Empty description="暂无数据" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<!-- 西药常用方 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
commonPrescriptionData.west_prescription &&
|
||||||
|
commonPrescriptionData.west_prescription.length > 0
|
||||||
|
"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<h3 class="mb-4 border-l-4 border-blue-500 pl-3 text-lg font-semibold">
|
||||||
|
西药常用方
|
||||||
|
</h3>
|
||||||
|
<Collapse>
|
||||||
|
<CollapsePanel
|
||||||
|
v-for="(item, index) in commonPrescriptionData.west_prescription"
|
||||||
|
:key="item.id"
|
||||||
|
:header="item.name || `西药处方 ${index + 1}`"
|
||||||
|
>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p v-if="item.clinical_diagnose">
|
||||||
|
<span class="font-medium">临床诊断:</span>
|
||||||
|
{{ item.clinical_diagnose }}
|
||||||
|
</p>
|
||||||
|
<p v-if="item.doctor_order">
|
||||||
|
<span class="font-medium">医嘱:</span>{{ item.doctor_order }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">药品:</span>
|
||||||
|
{{ getDrugNames(commonPrescriptionData.west[index]) }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="
|
||||||
|
handleEditCommonPrescription(
|
||||||
|
item,
|
||||||
|
commonPrescriptionData.west[index],
|
||||||
|
'west',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要删除这个常用方吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleDeleteCommonPrescription(item.id, 'west')"
|
||||||
|
>
|
||||||
|
<Button type="primary" danger size="small">删除</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 中药常用方 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
commonPrescriptionData.chin_prescription &&
|
||||||
|
commonPrescriptionData.chin_prescription.length > 0
|
||||||
|
"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<h3 class="mb-4 border-l-4 border-green-500 pl-3 text-lg font-semibold">
|
||||||
|
中药常用方
|
||||||
|
</h3>
|
||||||
|
<Collapse>
|
||||||
|
<CollapsePanel
|
||||||
|
v-for="(item, index) in commonPrescriptionData.chin_prescription"
|
||||||
|
:key="item.id"
|
||||||
|
:header="item.name || `中药处方 ${index + 1}`"
|
||||||
|
>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p v-if="item.clinical_diagnose">
|
||||||
|
<span class="font-medium">临床诊断:</span>
|
||||||
|
{{ item.clinical_diagnose }}
|
||||||
|
</p>
|
||||||
|
<p v-if="item.doctor_order">
|
||||||
|
<span class="font-medium">医嘱:</span>{{ item.doctor_order }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">药品:</span>
|
||||||
|
{{ getDrugNames(commonPrescriptionData.chinese[index], 'name') }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="
|
||||||
|
handleEditCommonPrescription(
|
||||||
|
item,
|
||||||
|
commonPrescriptionData.chinese[index],
|
||||||
|
'chinese',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要删除这个常用方吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleDeleteCommonPrescription(item.id, 'chinese')"
|
||||||
|
>
|
||||||
|
<Button type="primary" danger size="small">删除</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 颗粒药常用方 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
commonPrescriptionData.granular_prescription &&
|
||||||
|
commonPrescriptionData.granular_prescription.length > 0
|
||||||
|
"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<h3 class="mb-4 border-l-4 border-orange-500 pl-3 text-lg font-semibold">
|
||||||
|
颗粒药常用方
|
||||||
|
</h3>
|
||||||
|
<Collapse>
|
||||||
|
<CollapsePanel
|
||||||
|
v-for="(item, index) in commonPrescriptionData.granular_prescription"
|
||||||
|
:key="item.id"
|
||||||
|
:header="item.name || `颗粒药处方 ${index + 1}`"
|
||||||
|
>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p v-if="item.clinical_diagnose">
|
||||||
|
<span class="font-medium">临床诊断:</span>
|
||||||
|
{{ item.clinical_diagnose }}
|
||||||
|
</p>
|
||||||
|
<p v-if="item.doctor_order">
|
||||||
|
<span class="font-medium">医嘱:</span>{{ item.doctor_order }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="font-medium">药品:</span>
|
||||||
|
{{ getDrugNames(commonPrescriptionData.granular[index], 'name') }}
|
||||||
|
</p>
|
||||||
|
<div class="mt-4 flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="
|
||||||
|
handleEditCommonPrescription(
|
||||||
|
item,
|
||||||
|
commonPrescriptionData.granular[index],
|
||||||
|
'granular',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要删除这个常用方吗?"
|
||||||
|
ok-text="确定"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="handleDeleteCommonPrescription(item.id, 'granular')"
|
||||||
|
>
|
||||||
|
<Button type="primary" danger size="small">删除</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsePanel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无数据提示 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
(!commonPrescriptionData.west_prescription ||
|
||||||
|
commonPrescriptionData.west_prescription.length === 0) &&
|
||||||
|
(!commonPrescriptionData.chin_prescription ||
|
||||||
|
commonPrescriptionData.chin_prescription.length === 0) &&
|
||||||
|
(!commonPrescriptionData.granular_prescription ||
|
||||||
|
commonPrescriptionData.granular_prescription.length === 0)
|
||||||
|
"
|
||||||
|
class="text-center py-8"
|
||||||
|
>
|
||||||
|
<Empty description="暂无常用方,请在开处方时保存常用方" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
<Card v-else-if="doctorOrder && activeTabBar === 4" title="常用医嘱">
|
<Card v-else-if="doctorOrder && activeTabBar === 4" title="常用医嘱">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
@@ -366,6 +712,11 @@ const createDoctorOrder = () => {
|
|||||||
{{ item.disease.name }}
|
{{ item.disease.name }}
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- 常用方编辑弹窗 -->
|
||||||
|
<EditCommonPrescriptionModals />
|
||||||
|
<!-- 常用方新增弹窗 -->
|
||||||
|
<AddCommonPrescriptionModals />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user