Files
xk-admin/apps/web-antd/src/store/prescription.ts
李琦 50fc55925f feat: 地区选择器改走接口、订单市价重算与资金流水、同步日志内容复用
region-address-picker 接 region-picker API;订单支持按进价重算 market_price、追踪页资金流水;MES/ERP/中台同步日志共用内容查看组件;地区管理支持 pid 下钻与拼音检索。
2026-10-15 10:01:20 +08:00

1492 lines
49 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { computed, nextTick, ref, watch } from 'vue';
import { message, notification } from 'ant-design-vue';
import { debounce } from 'lodash-es';
import { defineStore } from 'pinia';
import { useUserStore as useVbenUserStore } from '@vben/stores';
import { useChatStore } from '#/views/business/chat/stores/chat';
import { useUserStore } from '#/views/business/chat/stores/user';
import { sendMessage } from '#/views/business/chat/utils/request';
import {
addWestPrescription,
checkChineseMedicineConflictApi,
getCurrentStoreTypeApi,
getPrescriptionTypeOptionsApi,
getDrugUseList,
getMyStoreListApi,
getPatientItem,
getProcessRuleList,
getProductListDoctorReception,
} from '#/views/doctor/doctor-reception/api';
import {
calcItemMarginPercent,
calcTotalMarginPercent,
isSeeRateEnabled,
} from '#/utils/chinesePrescriptionMargin';
import { calculateProcessingFee, parsePriceValue } from '#/utils/formatPrice';
import {
applySingleChildProcessRuleAutoSelect,
resolveProcessRuleLoadedLevel,
} from '#/utils/processRuleCascade';
import { getRegisterStoreInfo as getRegisterStoreInfoPharmacy } from '#/views/doctor/online-consultation/api';
import { getRegisterStoreInfo as getRegisterStoreInfoClinic } from '#/views/doctor/online-consultation-clinic/api';
import traditionalJson from '#/views/business/chat/config/traditional.json';
export const usePrescriptionStore = defineStore('prescription', () => {
// 基础数据
const patientInfo = ref<any | null>(null);
const userPatientHealthInquiry = ref<any | null>(null);
const prescriptionList = ref([]);
const currentRegisterId = ref<number | string>('');
const activePatient = ref<any | null>(null);
const userStore = useUserStore();
const chatStore = useChatStore();
// 药品使用相关数据
const drugUseNum = ref([]);
const drugUseFrequency = ref([]);
const drugUseType = ref([]);
const drugUnit = ref([]);
const drugTime = ref([]);
const drugUseWay = ref([]);
const myStoreList = ref([]);
// 处方状态
const myStoreId = ref(0);
const activeCategory = ref(2);
/** 后端下发的处方类型列表(含可选 icon / icon_text) */
const categories = ref([
{ label: '中药', value: 1, icon: '', icon_text: '' },
{ label: '西(中成)药', value: 2, icon: '', icon_text: '' },
{ label: '保健食品', value: 3, icon: '', icon_text: '' },
{ label: '产品服务包', value: 5, icon: '', icon_text: '' },
{ label: '非药品', value: 6, icon: '', icon_text: '' },
{ label: '医疗器械', value: 7, icon: '', icon_text: '' },
]);
const prescriptionTypeDefault = ref(2);
/** 开方种类权限校验中(进入开方页先 loading) */
const prescriptionTypeLoading = ref(false);
/** 当前门店是否有可开方种类 */
const prescriptionTypeAllowed = ref(true);
const prescriptionTypeDenyMessage = ref('');
const diagnosis = ref('');
const medicalAdvice = ref('');
const treatmentPrice = ref(0);
const ruleType = ref(1);
const drugList = ref([]);
const currentDrugs = ref([]);
const category = ref(1);
const selectProductId = ref(0);
// 中药相关配置
const processRuleList = ref([]);
const childProcessRuleList = ref([]);
const processRuleNoteList = ref([]);
const processRuleId = ref();
const processRuleNoteId = ref();
const childProcessRuleId = ref();
const packageMethodId = ref(2);
const dosage = ref(7);
const dayDosage = ref(2);
// 新药品相关
const newDrugInfo = ref<any>({});
const selectChineseIndex = ref(-1);
const selectChineseId = ref(0);
/** 中药气泡选中的完整商品行(添加时优先使用) */
const pendingNewChineseProduct = ref<any>(null);
// 二次签名相关
const doctorSecondSign = ref(0);
const checkData = ref<any>({});
// 初始化状态标记
const isInitialized = ref(false);
const isPatientInfoLoaded = ref(false);
const traditionalChineseMedicine = ref(traditionalJson);
const diseasesSelected = ref();
const methodSelected = ref();
const syndromeSelected = ref();
// 诊所选择相关状态
const registerStoreInfo = ref<any>(null); // 挂号诊所信息
const selectedStoreId = ref<number | null>(null); // 用户选择的诊所ID
const sendMode = ref(0); // 0=默认(使用医生当前诊所),1=自定义(使用挂号诊所)
// 存储前缀(用于区分在线复诊-药店和在线复诊-诊所)
const storagePrefix = ref('onlineConsultation-'); // 默认前缀
/** 当前取价门店是否允许查看毛利率(0/1,来自 xk-api) */
const seeRate = ref(0);
// 获取localStorage key(按类型分开存储)
const getStorageKey = (category?: number) => {
const cat = category ?? activeCategory.value;
return `${storagePrefix.value}prescriptionData_${cat}_${currentRegisterId.value}`;
};
// 计算属性
const totalProductCost = computed(() => {
if (currentDrugs.value.length === 0) {
return 0;
}
if (activeCategory.value === 1) {
return currentDrugs.value.reduce(
(sum, drug) => sum + drug.price * (drug.number || 1) * dosage.value,
0,
);
}
return currentDrugs.value.reduce(
(sum, drug) => sum + drug.price * (drug.select_number || 1),
0,
);
});
const processingFee = computed(() => {
if (ruleType.value !== 2 || currentDrugs.value.length === 0) return 0;
const processRule = childProcessRuleList.value.find(
(item) => item.id === childProcessRuleId.value,
);
const totalQty = currentDrugs.value.reduce(
(sum, drug) => sum + parsePriceValue(drug.number ?? 1),
0,
);
return calculateProcessingFee(processRule, dosage.value, totalQty);
});
const totalCost = computed(() => {
return totalProductCost.value + processingFee.value;
});
/** 实际履约/开方门店:自定义挂号店时用 selectedStoreId,否则用医生当前店 */
const resolvePrescribingStoreId = () => {
if (sendMode.value === 1 && selectedStoreId.value) {
return Number(selectedStoreId.value);
}
return Number(myStoreId.value || 0);
};
/** 供模板绑定:煎药预设等按此门店隔离加载 */
const prescribingStoreId = computed(() => resolvePrescribingStoreId());
const fetchStoreSeeRate = async () => {
try {
const params: { register_id?: number; store_id?: number } = {};
const sid = resolvePrescribingStoreId();
if (sid) {
params.store_id = sid;
}
if (currentRegisterId.value) {
params.register_id = Number(currentRegisterId.value);
}
const res = await getCurrentStoreTypeApi(params);
seeRate.value = Number(res?.see_rate ?? 0);
} catch (error) {
console.error('获取毛利率权限失败:', error);
}
};
watch(
[myStoreId, selectedStoreId, sendMode, currentRegisterId],
() => {
if (myStoreId.value) {
fetchStoreSeeRate();
}
},
);
const chineseGrossMarginPercent = computed(() => {
if (!isSeeRateEnabled(seeRate.value) || activeCategory.value !== 1) {
return null;
}
return calcTotalMarginPercent(currentDrugs.value, dosage.value);
});
const getChineseItemMargin = (drug: {
price?: number | string;
buy_price?: number | string;
}) => {
if (!isSeeRateEnabled(seeRate.value)) {
return null;
}
return calcItemMarginPercent(drug.price, drug.buy_price);
};
/**
* 拉取后端处方类型列表与默认选中;无绑定时清空 list 并返回 false
*/
const loadPrescriptionTypeOptions = async (registerId?: number | string) => {
prescriptionTypeLoading.value = true;
try {
const params: { register_id?: number; store_id?: number } = {};
const rid = Number(registerId || currentRegisterId.value);
if (rid) params.register_id = rid;
if (myStoreId.value) params.store_id = myStoreId.value;
const res = await getPrescriptionTypeOptionsApi(params);
const list = Array.isArray(res?.list) ? res.list : [];
categories.value = list.map((item) => ({
value: Number(item.value),
label: item.label || '',
icon: item.icon || '',
icon_text: item.icon_text || '',
}));
const def = Number(res?.default) || 0;
prescriptionTypeDefault.value = def;
const allowed =
res?.allowed !== false && list.length > 0;
prescriptionTypeAllowed.value = allowed;
prescriptionTypeDenyMessage.value = allowed
? ''
: (res?.message || '当前门店未开通开方权限');
// 当前 Tab 不在 allowed 内时强制切到 default
if (allowed && def && !categories.value.some((c) => c.value === activeCategory.value)) {
activeCategory.value = def;
}
return allowed;
} catch (error) {
console.error('加载处方类型失败:', error);
categories.value = [];
prescriptionTypeAllowed.value = false;
prescriptionTypeDenyMessage.value = '处方类型加载失败,请稍后重试';
message.warning('处方类型加载失败,请稍后重试');
return false;
} finally {
prescriptionTypeLoading.value = false;
}
};
// localStorage同步方法
const syncToLocalStorage = () => {
try {
const storageKey = getStorageKey();
localStorage.setItem(storageKey, JSON.stringify(currentDrugs.value));
console.log('已同步到localStorage:', storageKey, currentDrugs.value);
} catch (error) {
console.error('保存到localStorage失败:', error);
}
};
const loadFromLocalStorage = () => {
try {
const storageKey = getStorageKey();
const stored = localStorage.getItem(storageKey);
console.log('从localStorage加载:', storageKey, stored);
if (stored) {
const parsedData = JSON.parse(stored);
currentDrugs.value.splice(0, currentDrugs.value.length, ...parsedData);
} else {
currentDrugs.value.splice(0, currentDrugs.value.length);
}
} catch (error) {
console.error('从localStorage加载失败:', error);
currentDrugs.value.splice(0, currentDrugs.value.length);
}
};
// 更新currentDrugs并同步到localStorage
const updateCurrentDrugs = (newDrugs: any[]) => {
currentDrugs.value.splice(0, currentDrugs.value.length, ...newDrugs);
// 更新总价
syncToLocalStorage();
};
// 基础数据初始化(不包含患者信息)
const initializeBasicData = async () => {
if (isInitialized.value) {
console.log('基础数据已初始化,跳过重复请求');
return;
}
try {
await Promise.all([
getMyStoreList(),
getDrugUseListData(),
getProcessRuleListData(),
]);
isInitialized.value = true;
console.log('基础数据初始化完成');
} catch (error) {
console.error('基础数据初始化失败:', error);
}
};
// 完整初始化(包含患者信息,仅用于 PrescriptionModal)
const initializePrescription = async (
registerId: number | string,
shouldFetchPatientInfo: boolean = false,
prefix: string = 'onlineConsultation-'
) => {
// 设置存储前缀
storagePrefix.value = prefix;
// 设置当前注册ID
currentRegisterId.value = registerId;
// 先拉类型选项,再决定默认 Tab(无本地记忆时用后端 default)
if (!isInitialized.value) {
await initializeBasicData();
}
await loadPrescriptionTypeOptions(registerId);
// 恢复之前保存的 activeCategory
const savedCategory = localStorage.getItem(
`${storagePrefix.value}activeCategory${registerId}`
);
if (savedCategory) {
activeCategory.value = Number.parseInt(savedCategory);
} else {
// 检查哪个存储键有数据
const chineseData = localStorage.getItem(
`${storagePrefix.value}prescriptionData_1_${registerId}`
);
const westData = localStorage.getItem(
`${storagePrefix.value}prescriptionData_2_${registerId}`
);
if (chineseData && JSON.parse(chineseData).length > 0) {
activeCategory.value = 1;
} else if (westData && JSON.parse(westData).length > 0) {
activeCategory.value = 2;
} else if (prescriptionTypeDefault.value) {
activeCategory.value = prescriptionTypeDefault.value;
}
}
// 加载localStorage数据
loadFromLocalStorage();
await fetchStoreSeeRate();
// 获取患者信息(只在需要时调用,如 PrescriptionModal)
if (shouldFetchPatientInfo) {
if (
!isPatientInfoLoaded.value ||
patientInfo.value?.register_id !== registerId
) {
await getPatientInfo();
isPatientInfoLoaded.value = true;
}
}
};
// 轻量级初始化(仅用于 WesternModal 等子组件)
const initializeForModal = async (registerId: number | string, prefix: string = 'onlineConsultation-') => {
console.log('轻量级初始化,registerId:', registerId);
// 设置存储前缀
storagePrefix.value = prefix;
// 设置当前注册ID
currentRegisterId.value = registerId;
// 恢复之前保存的 activeCategory
const savedCategory = localStorage.getItem(
`${storagePrefix.value}activeCategory${registerId}`
);
if (savedCategory) {
activeCategory.value = Number.parseInt(savedCategory);
} else {
// 检查哪个存储键有数据
const chineseData = localStorage.getItem(
`${storagePrefix.value}prescriptionData_1_${registerId}`
);
const westData = localStorage.getItem(
`${storagePrefix.value}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数据
loadFromLocalStorage();
// 如果基础数据未初始化,先初始化基础数据
if (!isInitialized.value) {
await initializeBasicData();
}
};
/**
* 拉取医生门店列表并解析当前履约门店。
* 为什么:旧逻辑每次把 myStoreId 强制写成 res[0],换店/登录态门店会被冲掉,
* 煎药预设按 doctor+store 隔离时就会表现为「以前加的方案不见了」。
*/
const getMyStoreList = async () => {
try {
const res = await getMyStoreListApi();
myStoreList.value = res || [];
if (!myStoreList.value.length) {
console.log('获取诊所列表成功(空)');
return;
}
// 优先:当前已选 → 后台登录态门店 → 本地缓存;最后才回落列表第一项
const vbenStoreId = Number(
useVbenUserStore().userInfo?.store_id || 0,
);
const preferred =
Number(myStoreId.value || 0) ||
vbenStoreId ||
Number(localStorage.getItem('myStoreId') || 0);
const hit = myStoreList.value.find(
(s: any) => Number(s.id) === preferred,
);
myStoreId.value = hit
? Number(hit.id)
: Number(myStoreList.value[0].id);
console.log('获取诊所列表成功');
} catch (error) {
console.error('获取诊所列表失败:', error);
}
};
const getDrugUseListData = async () => {
try {
const res = await getDrugUseList();
drugUseNum.value = res.drug_use_num;
drugUseFrequency.value = res.drug_use_frequency;
drugUseType.value = res.drug_use_type;
drugUnit.value = res.drug_unit;
drugTime.value = res.drug_time;
drugUseWay.value = res.drug_use_way;
console.log('获取药品使用列表成功');
} catch (error) {
console.error('获取药品使用列表失败:', error);
}
};
const getProcessRuleListData = async (pid = 0, ruleId = 0) => {
try {
const data = ruleId === 0 ? { pid } : { rule_id: ruleId };
const res = await getProcessRuleList(data);
if (ruleId !== 0) {
processRuleNoteList.value = res;
} else if (pid === 0) {
processRuleList.value = res;
} else {
childProcessRuleList.value = res;
}
await applySingleChildProcessRuleAutoSelect(
resolveProcessRuleLoadedLevel(pid, ruleId),
{
ruleType: ruleType.value,
processRuleId: processRuleId.value,
childProcessRuleId: childProcessRuleId.value,
processRuleNoteId: processRuleNoteId.value,
processRuleList: processRuleList.value,
childProcessRuleList: childProcessRuleList.value,
processRuleNoteList: processRuleNoteList.value,
load: getProcessRuleListData,
setProcessRuleId: (id) => {
processRuleId.value = id;
},
setChildProcessRuleId: (id) => {
childProcessRuleId.value = id;
},
setProcessRuleNoteId: (id) => {
processRuleNoteId.value = id;
},
clearBelowProcessRule: () => {
childProcessRuleId.value = undefined;
processRuleNoteId.value = undefined;
childProcessRuleList.value = [];
processRuleNoteList.value = [];
},
clearBelowChild: () => {
processRuleNoteId.value = undefined;
processRuleNoteList.value = [];
},
},
);
console.log('获取加工规则成功');
} catch (error) {
console.error('获取加工规则失败:', error);
}
};
const getPatientInfo = async () => {
try {
console.log('开始获取患者信息,registerId:', currentRegisterId.value);
const res = await getPatientItem(currentRegisterId.value);
patientInfo.value = res;
activePatient.value = res.user_patient;
userPatientHealthInquiry.value = res.user_patient_health_inquiry;
prescriptionList.value = res.prescription;
console.log('获取患者信息成功:', activePatient.value);
} catch (error) {
console.error('获取患者信息失败:', error);
}
};
// 获取挂号诊所信息(同时包含当前诊所信息)
const fetchRegisterStoreInfo = async () => {
if (!currentRegisterId.value) {
console.warn('currentRegisterId为空,无法获取挂号诊所信息');
return;
}
try {
// 根据storagePrefix判断使用哪个API
const getRegisterStoreInfoFn = storagePrefix.value === 'onlineConsultationClinic-'
? getRegisterStoreInfoClinic
: getRegisterStoreInfoPharmacy;
const res = await getRegisterStoreInfoFn(Number(currentRegisterId.value));
registerStoreInfo.value = res;
// 如果是转诊挂号,自动使用承接方诊所ID(转接方开处方时)
if (res && res.is_from_transfer === 1 && res.delegate_store_id) {
// 转接方开处方时,自动设置store_id为承接方诊所ID
selectedStoreId.value = res.delegate_store_id;
sendMode.value = 1; // 使用自定义模式,但使用承接方诊所ID
} else if (res && res.store_id) {
selectedStoreId.value = res.store_id;
sendMode.value = 1;
}
console.log('获取挂号诊所信息成功:', res);
await fetchStoreSeeRate();
} catch (error) {
console.error('获取挂号诊所信息失败:', error);
}
};
// 切换诊所选择
const toggleStoreSelection = (useRegisterStore: boolean) => {
if (useRegisterStore && registerStoreInfo.value?.store_id) {
selectedStoreId.value = registerStoreInfo.value.store_id;
sendMode.value = 1;
} else {
selectedStoreId.value = null;
sendMode.value = 0;
}
};
// 计算当前诊所名称(医生所属诊所,优先使用后端返回的数据)
const currentStoreName = computed(() => {
// 优先使用后端返回的当前诊所名称
if (registerStoreInfo.value?.current_store_name) {
return registerStoreInfo.value.current_store_name;
}
// 回退到本地诊所列表
const currentStore = myStoreList.value.find((s: any) => s.id === myStoreId.value);
return currentStore?.name || '当前诊所';
});
// 计算目标诊所名称(根据发送模式决定)
const targetStoreName = computed(() => {
if (sendMode.value === 1 && registerStoreInfo.value?.store_name) {
return registerStoreInfo.value.store_name;
}
// 默认使用医生当前诊所
return currentStoreName.value;
});
// 是否可以更改诊所(转诊挂号时不允许更改,使用承接方诊所ID)
const canChangeStore = computed(() => {
// 如果是转诊挂号,不允许更改诊所(自动使用承接方诊所ID)
if (registerStoreInfo.value?.is_from_transfer === 1) {
return false;
}
// 非转诊挂号,使用后端返回的 can_change 字段
return registerStoreInfo.value?.can_change === true;
});
// 药品搜索
const getDrugList = debounce(async (searchText = '') => {
if (searchText === '' && activeCategory.value === 1) {
drugList.value = [];
return;
}
try {
const res = await getProductListDoctorReception({
store_id: myStoreId.value,
type: activeCategory.value,
name: searchText,
...(currentRegisterId.value
? { register_id: Number(currentRegisterId.value) }
: {}),
});
drugList.value = res.map((item) => {
const matched = currentDrugs.value.find((v) => v.index_id === item.id);
return {
...item,
select_number: matched?.select_number || 0,
drug: {
...item.drug,
number: matched?.number || 1,
},
};
});
} catch (error) {
console.error('获取药品列表失败:', error);
message.error('获取药品列表失败');
}
}, 300);
/**
* 从商品/药品数据解析处方分类 tab(与 activeCategory 对齐)
* 1 中药 / 2 西药 / 3 保健食品 / 5 产品服务包 / 6 非药品 / 7 医疗器械
* 缺省按西药 2,避免挂号选药误入中药开方
*/
const resolveDrugCategory = (data: any): number => {
const raw = data?.drug?.type ?? data?.type;
const n = Number(raw);
if (Number.isFinite(n) && n > 0) return n;
return 2;
};
// 药品操作:写入前按药型自动切到对应 tab,避免西药落入中药开方组件
const addProducts = (data: any) => {
const targetCategory = resolveDrugCategory(data);
if (Number(activeCategory.value) !== targetCategory) {
changeCategory(targetCategory);
}
const existItem = currentDrugs.value.find(
(item) => item.index_id === data.id,
);
if (existItem) {
message.warn('已经存在了');
return false;
}
const newProduct = {
index_id: data.id,
id: data.drug.id,
drug_name: data.drug.drug_name,
number: data.drug.number,
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
use_frequency: drugUseFrequency.value.find(
(item) => item.id === data.drug.frequency_id,
),
unit:
data.drug?.unit ||
drugUnit.value.find((item) => item.id === data.drug.unit_id),
price: data.price,
buy_price: data.buy_price,
way_id: data.drug?.way_id,
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
time_id: data.drug.time_id,
type_id: data.drug.type_id,
frequency_id: data.drug.frequency_id,
unit_id: data.drug.unit_id,
image: data.drug.image,
// 规格(选仓弹窗等处展示)
specification: data.drug?.specification || data.specification || '',
instruction: data.drug.instruction,
type: data.drug.type,
select_number: (() => {
const raw = Number(
data.select_number ?? data.purchase_quantity ?? data.purchase_number,
);
if (!Number.isFinite(raw) || raw < 1) return 1;
return Math.min(9999, Math.max(1, Math.floor(raw)));
})(),
};
const newDrugs = [...currentDrugs.value, newProduct];
updateCurrentDrugs(newDrugs);
message.success(`已将${newProduct.drug_name}添加到清单中!`);
return true;
};
const removeDrug = (index: number) => {
const newDrugs = currentDrugs.value.filter((_, i) => i !== index);
updateCurrentDrugs(newDrugs);
};
const updateDrugQuantity = (index: number, quantity: number) => {
if (quantity > 0) {
const newDrugs = [...currentDrugs.value];
newDrugs[index].select_number = quantity;
updateCurrentDrugs(newDrugs);
}
};
const increment = (index: number) => {
const newDrugs = [...currentDrugs.value];
newDrugs[index].select_number++;
updateCurrentDrugs(newDrugs);
};
const decrement = (index: number) => {
if (currentDrugs.value[index].select_number > 1) {
const newDrugs = [...currentDrugs.value];
newDrugs[index].select_number--;
updateCurrentDrugs(newDrugs);
}
};
// 切换药品用法编辑状态
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);
};
/**
* 中药气泡:选中新增行药名
* @param item 商品检索完整行
*/
const onSelectNewChineseDrug = (item: any) => {
const drugId = Number(item?.drug_id || item?.drug?.id || 0);
if (!drugId) return;
const check = currentDrugs.value.find((v) => Number(v.id) === drugId);
if (check) {
newDrugInfo.value.id = '';
newDrugInfo.value.name = '';
pendingNewChineseProduct.value = null;
message.error('该药品已经在处方中了!');
return;
}
pendingNewChineseProduct.value = item;
newDrugInfo.value.id = drugId;
newDrugInfo.value.price = item.price;
newDrugInfo.value.name = item.drug?.drug_name || '';
newDrugInfo.value.unit =
item.drug?.unit ||
drugUnit.value.find((u) => u.id === item.drug?.unit_id);
newDrugInfo.value.unit_id = item.drug?.unit_id;
nextTick(() => {
const input = document.querySelector(
'.new-number-input input',
) as HTMLElement;
input?.focus();
});
};
/**
* 中药气泡:替换已选行药名
* @param index 行下标
* @param item 商品检索完整行
*/
const onSelectOldChineseDrug = (index: number, item: any) => {
const drugId = Number(item?.drug?.id || item?.drug_id || 0);
if (!drugId) return;
const check = currentDrugs.value.find(
(v, i) => i !== index && Number(v.id) === drugId,
);
if (check) {
message.error('该药品已经在处方中了!');
return;
}
const data = item;
const newProduct = {
index_id: data.id,
id: data.drug.id,
drug_name: data.drug.drug_name,
number: data.drug.number,
use_num: drugTime.value.find((u) => u.id === data.drug.time_id),
use_type: drugUseType.value.find((u) => u.id === data.drug.type_id),
use_frequency: drugUseFrequency.value.find(
(u) => u.id === data.drug.frequency_id,
),
unit:
data.drug?.unit ||
drugUnit.value.find((u) => u.id === data.drug.unit_id),
price: data.price,
buy_price: data.buy_price,
way_id: data.drug?.way_id,
use_ways: drugUseWay.value.find((u) => u.id === data.drug.way_id),
time_id: data.drug.time_id,
type_id: data.drug.type_id,
frequency_id: data.drug.frequency_id,
unit_id: data.drug.unit_id,
image: data.drug.image,
specification: data.drug?.specification || data.specification || '',
instruction: data.drug.instruction,
type: data.drug.type,
};
const newDrugs = [...currentDrugs.value];
newDrugs[index] = newProduct;
updateCurrentDrugs(newDrugs);
nextTick(() => {
const input = document.querySelector(
`.old-number-input-${index} input`,
) as HTMLElement;
input?.focus();
});
};
// 新药品操作(Select 兼容)
const selectNewDrugInfo = () => {
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
if (check) {
newDrugInfo.value.id = '';
message.error('该药品已经在处方中了!');
return;
}
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
if (data) {
pendingNewChineseProduct.value = data;
newDrugInfo.value.price = data.price;
newDrugInfo.value.name = data.drug.drug_name;
// 选药后立刻带上真实单位,供新行数量后缀展示
newDrugInfo.value.unit =
data.drug?.unit ||
drugUnit.value.find((item) => item.id === data.drug?.unit_id);
newDrugInfo.value.unit_id = data.drug?.unit_id;
nextTick(() => {
const input = document.querySelector(
'.new-number-input input',
) as HTMLElement;
input?.focus();
});
}
};
const selectOldDrugInfo = (id: number) => {
const check = currentDrugs.value.find((v) => v.id === id);
if (check) {
const newDrugs = [...currentDrugs.value];
newDrugs[selectChineseIndex.value].id = selectChineseId.value;
updateCurrentDrugs(newDrugs);
drugList.value = [];
message.error('该药品已经在处方中了!');
return;
}
const data = drugList.value.find((v) => v.drug_id === id);
if (data) {
const newProduct = {
index_id: data.id,
id: data.drug.id,
drug_name: data.drug.drug_name,
number: data.drug.number,
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
use_type: drugUseType.value.find(
(item) => item.id === data.drug.type_id,
),
use_frequency: drugUseFrequency.value.find(
(item) => item.id === data.drug.frequency_id,
),
unit:
data.drug?.unit ||
drugUnit.value.find((item) => item.id === data.drug.unit_id),
price: data.price,
buy_price: data.buy_price,
way_id: data.drug?.way_id,
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
time_id: data.drug.time_id,
type_id: data.drug.type_id,
frequency_id: data.drug.frequency_id,
unit_id: data.drug.unit_id,
image: data.drug.image,
// 规格(选仓弹窗等处展示)
specification: data.drug?.specification || data.specification || '',
instruction: data.drug.instruction,
type: data.drug.type,
};
const newDrugs = [...currentDrugs.value];
newDrugs[selectChineseIndex.value] = newProduct;
updateCurrentDrugs(newDrugs);
nextTick(() => {
const input = document.querySelector(
`.old-number-input-${selectChineseIndex.value} input`,
) as HTMLElement;
input?.focus();
});
}
};
const setSelectChineseIndex = (index: number, id: number) => {
selectChineseIndex.value = index;
selectChineseId.value = id;
};
const addDrugByChinese = () => {
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
if (check) {
message.error('该药品已经在处方中了!');
return false;
}
const data =
pendingNewChineseProduct.value ||
drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
if (!data) {
message.error('请选择药品');
return false;
}
data.drug.number = newDrugInfo.value.number;
data.drug.way_id = newDrugInfo.value.way_id;
const success = addProducts(data);
if (success) {
newDrugInfo.value = {};
pendingNewChineseProduct.value = null;
}
return success;
};
const selectDrugByNewDrugInfo = (
event: KeyboardEvent,
isNewDrug: boolean,
) => {
if (event.key === 'Enter') {
event.preventDefault();
if (isNewDrug) {
addDrugByChinese();
} else {
syncToLocalStorage();
}
nextTick(() => {
const input = document.querySelector(
'.new-select-drug-name input',
) as HTMLElement;
input?.focus();
});
}
};
const updateChineseNumber = () => {
syncToLocalStorage();
};
const updateChineseNumberGoNewDrug = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
syncToLocalStorage();
nextTick(() => {
const input = document.querySelector(
'.new-select-drug-name input',
) as HTMLElement;
input?.focus();
});
}
};
// 药品用法选择
const selectProductChange = (id: number) => {
selectProductId.value = id;
};
const selectDrugUseWayChange = (id: number) => {
const newDrugs = currentDrugs.value.map((item) =>
item.index_id === selectProductId.value
? {
...item,
way_id: id,
use_ways: drugUseWay.value.find((value) => value.id === id),
}
: item,
);
updateCurrentDrugs(newDrugs);
};
// 加工规则操作
const selectProcessRule = (id: number) => {
processRuleId.value = id;
getProcessRuleListData(id);
};
const selectProcessRuleNot = (id: number) => {
childProcessRuleId.value = id;
getProcessRuleListData(0, id);
};
const selectProcessRuleNotCommit = (id: number) => {
processRuleNoteId.value = id;
};
const selectPackageMethod = (id: number) => {
packageMethodId.value = id;
};
/**
* 中药配置变更(标签选择,对齐移动端级联逻辑)
*/
const handleChineseConfigChange = async (payload: {
ruleType?: number;
packageMethodId?: number | null;
processRuleId?: number | null;
childProcessRuleId?: number | null;
processRuleNoteId?: number | null;
dosage?: number;
dayDosage?: number;
}) => {
const oldRuleType = ruleType.value;
const oldProcessRuleId = processRuleId.value;
const oldChildProcessRuleId = childProcessRuleId.value;
// 预设整包回填时同一次已带下级 ID,勿误清空(对齐接诊页 / 小程序)
const keepChild =
payload.childProcessRuleId != null && Number(payload.childProcessRuleId) > 0;
const keepNote =
payload.processRuleNoteId != null && Number(payload.processRuleNoteId) > 0;
if (payload.ruleType === 2 && payload.ruleType !== oldRuleType) {
if (processRuleList.value.length === 0) {
await getProcessRuleListData();
}
}
if (payload.ruleType === 1 && oldRuleType === 2) {
processRuleId.value = undefined;
childProcessRuleId.value = undefined;
processRuleNoteId.value = undefined;
processRuleList.value = [];
childProcessRuleList.value = [];
processRuleNoteList.value = [];
}
if (payload.processRuleId != null && payload.processRuleId !== oldProcessRuleId) {
if (!keepChild) {
childProcessRuleId.value = undefined;
processRuleNoteId.value = undefined;
}
childProcessRuleList.value = [];
processRuleNoteList.value = [];
}
if (payload.childProcessRuleId != null && payload.childProcessRuleId !== oldChildProcessRuleId) {
if (!keepNote) {
processRuleNoteId.value = undefined;
}
processRuleNoteList.value = [];
}
if (payload.ruleType != null) ruleType.value = payload.ruleType;
if (payload.packageMethodId !== undefined) packageMethodId.value = payload.packageMethodId ?? 2;
if (payload.processRuleId !== undefined) processRuleId.value = payload.processRuleId ?? undefined;
if (payload.childProcessRuleId !== undefined) childProcessRuleId.value = payload.childProcessRuleId ?? undefined;
if (payload.processRuleNoteId !== undefined) processRuleNoteId.value = payload.processRuleNoteId ?? undefined;
if (payload.dosage != null) dosage.value = payload.dosage;
if (payload.dayDosage != null) dayDosage.value = payload.dayDosage;
if (payload.processRuleId != null && payload.processRuleId !== oldProcessRuleId) {
await getProcessRuleListData(payload.processRuleId);
}
if (payload.childProcessRuleId != null && payload.childProcessRuleId !== oldChildProcessRuleId) {
await getProcessRuleListData(0, payload.childProcessRuleId);
}
};
// 中药相冲检查
const checkChineseMedicineConflict = async () => {
if (activeCategory.value === 2) {
return { hasConflict: false };
}
notification.info({
message: '正在检查药物相冲',
duration: 1,
description: '正在检查药物相冲,请稍等...',
});
try {
const names = currentDrugs.value.map((item) => item.drug_name);
const res = await checkChineseMedicineConflictApi({ names });
if (res.is_exist === true) {
checkData.value = { message: res.message };
return { hasConflict: true, message: res.message };
} else {
notification.success({
message: '检查成功',
duration: 3,
description: '暂无相冲药品',
});
return { hasConflict: false };
}
} catch (error) {
console.error('检查药物相冲失败:', error);
return { hasConflict: false };
}
};
// 发送处方
const sendPrescription = async (
doctorSecondSignValue = 0,
customSendMode: number = 0,
customStoreId: number | null = null,
warehouseMap: Record<number, number> | null = null,
) => {
if (currentDrugs.value.length === 0) {
message.error('请选择药品');
return false;
}
if (activeCategory.value === 1) {
if (ruleType.value === 1 && packageMethodId.value == null) {
message.error('请选择包法');
return false;
}
if (ruleType.value === 2) {
if (processRuleId.value == null) {
message.error('请选择制剂');
return false;
}
if (processRuleNoteId.value == null) {
message.error('请选择规格');
return false;
}
if (childProcessRuleId.value == null) {
message.error('请选择备注');
return false;
}
}
}
if (!diagnosis.value) {
message.error('诊断结果不能为空');
return false;
}
if (!medicalAdvice.value) {
message.error('医嘱不能为空');
return false;
}
try {
// 如果是转诊挂号,自动使用承接方诊所ID(转接方开处方时)
let finalSendMode = customSendMode;
let finalStoreId = customStoreId;
if (registerStoreInfo.value?.is_from_transfer === 1 && registerStoreInfo.value?.delegate_store_id) {
// 转接方开处方时,自动使用承接方诊所ID,不需要在模态框选择
finalSendMode = 1;
finalStoreId = registerStoreInfo.value.delegate_store_id;
}
const response = await addWestPrescription({
patient: activePatient.value,
drugs: currentDrugs.value,
diagnosis: diagnosis.value,
medicalAdvice: medicalAdvice.value,
total: totalCost.value,
category: category.value,
drug_type: 2,
register_id: currentRegisterId.value,
treatment_price: treatmentPrice.value,
package_method_id: packageMethodId.value,
process_rule_id: processRuleId.value,
process_rule_note_id: processRuleNoteId.value,
child_process_rule_id: childProcessRuleId.value,
process_rule_type: ruleType.value,
prescription_type: activeCategory.value,
processing_fee: processingFee.value,
dosage: dosage.value,
day_dosage: dayDosage.value,
doctor_second_sign: doctorSecondSignValue,
// 中医在线问诊所需的东西
diseases_id: diseasesSelected.value,
method_id: methodSelected.value,
syndrome_id: syndromeSelected.value,
// 诊所选择参数(转诊挂号时自动使用承接方诊所ID)
send_mode: finalSendMode,
custom_store_id: finalSendMode === 1 ? finalStoreId : null,
// 在线复诊手选仓:列表格式避免数字键对象序列化丢失
warehouse_map: warehouseMap
? Object.entries(warehouseMap).map(([drug_id, warehouse_id]) => ({
drug_id: Number(drug_id),
warehouse_id: Number(warehouse_id),
}))
: undefined,
});
const res = await response;
message.success('处方已发送');
sendMessage({
roomId: chatStore.currentFriend.room_id,
senderId: userStore.currentUser.doctor_id,
receiverId: chatStore.currentFriend.id,
type: 'prescription',
content: JSON.stringify(res),
});
chatStore.addMessage(
{
id: Date.now().toString(),
room_id: chatStore.currentFriend.room_id,
sender_user_id: `doctor-${userStore.currentUser.doctor_id}`,
receiver_user_id: chatStore.currentFriend.id,
message_type: 4,
message_content: JSON.stringify(res),
messageTypeName: 'prescription',
created_at: Date.now(),
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
timestamp: Date.now(),
isSent: true,
read: true,
duration: 0,
},
userStore.currentUser.doctor_id,
);
// 在线复诊开方后自动连发购药指引与用药温馨提示(小助手发送)
const tipTexts = [
'根据您的病情描述,已为您开具电子处方,请点击上方的‘立即支付’,进行购药;如有商品价格和订单等问题请咨询平台客服。',
'温馨提示:用药前请详细阅读药品说明书,并严格按照线下医嘱用药。如用药过程中出现病情变化或其它不适症状,请立即停药并及时就医。(请您注意,如果您尚未在医院就诊或未曾使用过本次申请的药品,请暂时不要支付订单。我们建议您在医生的指导下使用药品,以确保用药安全。)',
];
tipTexts.forEach((tip, tipIdx) => {
sendMessage({
roomId: chatStore.currentFriend.room_id,
senderId: 'doctor_assistant',
receiverId: chatStore.currentFriend.id,
type: 'text',
content: tip,
});
chatStore.addMessage(
{
id: `${Date.now()}_tip_${tipIdx}`,
room_id: chatStore.currentFriend.room_id,
sender_user_id: 'doctor_assistant',
receiver_user_id: chatStore.currentFriend.id,
message_type: 0,
message_content: tip,
messageTypeName: 'text',
created_at: Date.now() + tipIdx + 1,
created_at_text: new Date().toLocaleTimeString().slice(0, 5),
timestamp: Date.now() + tipIdx + 1,
isSent: true,
read: true,
duration: 0,
},
userStore.currentUser.doctor_id,
);
});
resetForm();
// 发送成功后清空本挂号下全部分类草稿(含另一分类与 activeCategory)
clearLocalPrescriptionCache();
// 返回包含转诊信息的响应数据
return {
success: true,
result: res?.result || res?.data || res,
need_transfer: res?.result?.need_transfer || res?.data?.need_transfer || res?.need_transfer,
transfer_prescription_id: res?.result?.transfer_prescription_id || res?.data?.transfer_prescription_id || res?.transfer_prescription_id,
};
} catch (error) {
console.error('发送处方失败:', error);
message.error('发送处方失败');
return false;
}
};
/**
* 清除当前挂号会话的处方 localStorage 草稿
* 原因:resetForm 只会把当前分类写成空数组,另一分类与 activeCategory 会残留,再次打开仍会回填
*/
const clearLocalPrescriptionCache = () => {
const registerId = currentRegisterId.value;
if (!registerId) return;
try {
// 与小程序 PrescriptionStorage.clearAllPrescriptionData 对齐的分类集合
const categories = [1, 2, 3, 5, 6, 7];
categories.forEach((cat) => {
localStorage.removeItem(
`${storagePrefix.value}prescriptionData_${cat}_${registerId}`,
);
// 清理历史双横线孤儿 key(旧版弹窗 `${prefix}-prescriptionData_`)
localStorage.removeItem(
`${storagePrefix.value}-prescriptionData_${cat}_${registerId}`,
);
});
localStorage.removeItem(
`${storagePrefix.value}activeCategory${registerId}`,
);
} catch (error) {
console.error('清除处方本地缓存失败:', error);
}
};
const resetForm = () => {
updateCurrentDrugs([]);
diagnosis.value = '';
medicalAdvice.value = '';
packageMethodId.value = 2;
dosage.value = 7;
dayDosage.value = 2;
newDrugInfo.value = {};
doctorSecondSign.value = 0;
};
const changeCategory = (categoryValue: number) => {
// 先保存当前类型的药品数据
localStorage.setItem(
getStorageKey(activeCategory.value),
JSON.stringify(currentDrugs.value),
);
// 保存 Tab 类型
localStorage.setItem(
`${storagePrefix.value}activeCategory${currentRegisterId.value}`,
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);
}
// 切换后聚焦 Tab 栏当前项
nextTick(() => {
const el = document.getElementById(`rx-modal-tab-${categoryValue}`);
el?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
});
};
// 工具函数
const splitString = (str: string) => {
if (!str) return [];
return str.split(',');
};
const refreshCurrentDrugs = () => {
loadFromLocalStorage();
};
// 重置初始化状态(用于切换患者时)
const resetInitializationState = () => {
isInitialized.value = false;
isPatientInfoLoaded.value = false;
patientInfo.value = null;
activePatient.value = null;
userPatientHealthInquiry.value = null;
prescriptionList.value = [];
console.log('重置初始化状态');
};
return {
// 状态
patientInfo,
userPatientHealthInquiry,
prescriptionList,
currentRegisterId,
activePatient,
drugUseNum,
drugUseFrequency,
drugUseType,
drugUnit,
drugTime,
drugUseWay,
myStoreList,
myStoreId,
activeCategory,
categories,
prescriptionTypeDefault,
prescriptionTypeLoading,
prescriptionTypeAllowed,
prescriptionTypeDenyMessage,
diagnosis,
medicalAdvice,
treatmentPrice,
ruleType,
drugList,
currentDrugs,
category,
selectProductId,
processRuleList,
childProcessRuleList,
processRuleNoteList,
processRuleId,
processRuleNoteId,
childProcessRuleId,
packageMethodId,
dosage,
dayDosage,
newDrugInfo,
selectChineseIndex,
selectChineseId,
doctorSecondSign,
checkData,
// 计算属性
totalProductCost,
processingFee,
totalCost,
seeRate,
chineseGrossMarginPercent,
getChineseItemMargin,
fetchStoreSeeRate,
// 方法
initializePrescription,
initializeForModal,
initializeBasicData,
loadPrescriptionTypeOptions,
resetInitializationState,
getDrugList,
addProducts,
resolveDrugCategory,
removeDrug,
updateDrugQuantity,
increment,
decrement,
toggleEditDrug,
saveEditDrug,
updateDrugUsage,
selectNewDrugInfo,
selectOldDrugInfo,
onSelectNewChineseDrug,
onSelectOldChineseDrug,
setSelectChineseIndex,
addDrugByChinese,
selectDrugByNewDrugInfo,
updateChineseNumber,
updateChineseNumberGoNewDrug,
selectProductChange,
selectDrugUseWayChange,
selectProcessRule,
selectProcessRuleNot,
selectProcessRuleNotCommit,
selectPackageMethod,
handleChineseConfigChange,
checkChineseMedicineConflict,
sendPrescription,
resetForm,
clearLocalPrescriptionCache,
changeCategory,
getProcessRuleListData,
splitString,
refreshCurrentDrugs,
syncToLocalStorage,
loadFromLocalStorage,
updateCurrentDrugs,
traditionalChineseMedicine,
diseasesSelected,
methodSelected,
syndromeSelected,
// 诊所选择相关
registerStoreInfo,
selectedStoreId,
sendMode,
resolvePrescribingStoreId,
prescribingStoreId,
fetchRegisterStoreInfo,
toggleStoreSelection,
currentStoreName,
targetStoreName,
canChangeStore,
};
});