diff --git a/apps/web-antd/src/store/prescription.ts b/apps/web-antd/src/store/prescription.ts
index 8fcfc114..b542602f 100644
--- a/apps/web-antd/src/store/prescription.ts
+++ b/apps/web-antd/src/store/prescription.ts
@@ -11,6 +11,7 @@ import {
addWestPrescription,
checkChineseMedicineConflictApi,
getCurrentStoreTypeApi,
+ getPrescriptionTypeOptionsApi,
getDrugUseList,
getMyStoreListApi,
getPatientItem,
@@ -54,6 +55,16 @@ export const usePrescriptionStore = defineStore('prescription', () => {
// 处方状态
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);
const diagnosis = ref('');
const medicalAdvice = ref('');
const treatmentPrice = ref(0);
@@ -192,6 +203,33 @@ export const usePrescriptionStore = defineStore('prescription', () => {
return calcItemMarginPercent(drug.price, drug.buy_price);
};
+ /**
+ * 拉取后端处方类型列表与默认选中
+ */
+ const loadPrescriptionTypeOptions = async (registerId?: number | string) => {
+ 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 : [];
+ if (list.length) {
+ 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);
+ if (def) prescriptionTypeDefault.value = def;
+ } catch (error) {
+ console.error('加载处方类型失败:', error);
+ message.warning('处方类型加载失败,请稍后重试');
+ }
+ };
+
// localStorage同步方法
const syncToLocalStorage = () => {
try {
@@ -260,6 +298,12 @@ export const usePrescriptionStore = defineStore('prescription', () => {
// 设置当前注册ID
currentRegisterId.value = registerId;
+ // 先拉类型选项,再决定默认 Tab(无本地记忆时用后端 default)
+ if (!isInitialized.value) {
+ await initializeBasicData();
+ }
+ await loadPrescriptionTypeOptions(registerId);
+
// 恢复之前保存的 activeCategory
const savedCategory = localStorage.getItem(
`${storagePrefix.value}activeCategory${registerId}`
@@ -278,17 +322,14 @@ export const usePrescriptionStore = defineStore('prescription', () => {
activeCategory.value = 1;
} else if (westData && JSON.parse(westData).length > 0) {
activeCategory.value = 2;
+ } else if (prescriptionTypeDefault.value) {
+ activeCategory.value = prescriptionTypeDefault.value;
}
}
// 加载localStorage数据
loadFromLocalStorage();
- // 如果基础数据未初始化,先初始化基础数据
- if (!isInitialized.value) {
- await initializeBasicData();
- }
-
await fetchStoreSeeRate();
// 获取患者信息(只在需要时调用,如 PrescriptionModal)
@@ -1113,6 +1154,11 @@ export const usePrescriptionStore = defineStore('prescription', () => {
} 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' });
+ });
};
// 工具函数
@@ -1152,6 +1198,8 @@ export const usePrescriptionStore = defineStore('prescription', () => {
myStoreList,
myStoreId,
activeCategory,
+ categories,
+ prescriptionTypeDefault,
diagnosis,
medicalAdvice,
treatmentPrice,
@@ -1188,6 +1236,7 @@ export const usePrescriptionStore = defineStore('prescription', () => {
initializePrescription,
initializeForModal,
initializeBasicData,
+ loadPrescriptionTypeOptions,
resetInitializationState,
getDrugList,
addProducts,
diff --git a/apps/web-antd/src/views/business/chat/components/PrescriptionModal.vue b/apps/web-antd/src/views/business/chat/components/PrescriptionModal.vue
index 17b430a1..520a5379 100644
--- a/apps/web-antd/src/views/business/chat/components/PrescriptionModal.vue
+++ b/apps/web-antd/src/views/business/chat/components/PrescriptionModal.vue
@@ -77,18 +77,15 @@ const commonPrescriptionName = ref('');
/** 是否正在保存常用方 */
const isSavingCommonPrescription = ref(false);
-const categories = [
- { label: '中药', value: 1 },
- { label: '中成(西)药', value: 2 },
- { label: '保健食品', value: 3 },
- { label: '产品服务包', value: 5 },
- { label: '非药品', value: 6 },
- { label: '医疗器械', value: 7 },
-];
-
// 使用 Pinia store
const prescriptionStore = usePrescriptionStore();
+/** 处方类型来自后端下发(store.categories) */
+const categories = computed(() => prescriptionStore.categories);
+
+const rxSwipeStartX = ref(0);
+const rxSwipeStartY = ref(0);
+
const allowInsuranceCategory = computed(
() => Number(prescriptionStore.registerStoreInfo?.allow_insurance_category ?? 0) === 1,
);
@@ -133,6 +130,8 @@ const [Modal, modalApi] = useVbenModal({
storagePrefix,
);
prescriptionStore.loadFromLocalStorage();
+ // 开方弹窗打开时再拉一次类型,确保 Network 可见且数据最新
+ await prescriptionStore.loadPrescriptionTypeOptions(modalData.value.registerId);
// 获取挂号诊所信息
await prescriptionStore.fetchRegisterStoreInfo();
@@ -446,6 +445,26 @@ const tabChange = async (id: number) => {
}
};
+/** 记录左右滑起点 */
+const onRxPanelPointerDown = (e: PointerEvent) => {
+ rxSwipeStartX.value = e.clientX;
+ rxSwipeStartY.value = e.clientY;
+};
+
+/** 左右滑切换相邻处方类型 */
+const onRxPanelPointerUp = (e: PointerEvent) => {
+ const dx = e.clientX - rxSwipeStartX.value;
+ const dy = e.clientY - rxSwipeStartY.value;
+ if (Math.abs(dx) < 80 || Math.abs(dx) <= Math.abs(dy)) return;
+ const list = categories.value || [];
+ if (list.length < 2) return;
+ const idx = list.findIndex((c) => Number(c.value) === Number(prescriptionStore.activeCategory));
+ if (idx < 0) return;
+ const nextIdx = dx < 0 ? idx + 1 : idx - 1;
+ if (nextIdx < 0 || nextIdx >= list.length) return;
+ tabChange(list[nextIdx].value);
+};
+
/**
* 检测并显示转诊提示
* @description 当切换到中药处方时,如果当前诊所为西医诊所,显示转诊提示
@@ -1021,16 +1040,30 @@ const cancelSaveCommonPrescription = () => {
+
+
@@ -1067,6 +1100,13 @@ const cancelSaveCommonPrescription = () => {
+
+
+ 已选 {{ prescriptionStore.currentDrugs.length }} 种药
+
{
+
@@ -1697,6 +1738,8 @@ const cancelSaveCommonPrescription = () => {
display: flex;
gap: 1rem;
/* margin: 1rem 0; 已在类中通过 sticky 处理 */
+ overflow-x: auto;
+ flex-wrap: nowrap;
}
.drug-categories button {
@@ -1707,6 +1750,11 @@ const cancelSaveCommonPrescription = () => {
background: transparent;
color: inherit;
transition: all 0.3s;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ flex-shrink: 0;
+ white-space: nowrap;
}
.dark .drug-categories button {
@@ -1720,6 +1768,25 @@ const cancelSaveCommonPrescription = () => {
background: #455cda;
}
+.tab-type-icon {
+ width: 16px;
+ height: 16px;
+ object-fit: contain;
+}
+
+.tab-type-badge {
+ font-size: 11px;
+ color: #e6a23c;
+ background: #fdf6ec;
+ padding: 0 6px;
+ border-radius: 4px;
+ line-height: 1.4;
+}
+
+.rx-swipe-panel {
+ touch-action: pan-y;
+}
+
.selected-drugs {
margin: 1rem 0;
border: 1px solid #eee;
diff --git a/apps/web-antd/src/views/business/express/express-company/components/modal.vue b/apps/web-antd/src/views/business/express/express-company/components/modal.vue
index b979ff65..509e74c9 100644
--- a/apps/web-antd/src/views/business/express/express-company/components/modal.vue
+++ b/apps/web-antd/src/views/business/express/express-company/components/modal.vue
@@ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({
});
-
+
diff --git a/apps/web-antd/src/views/business/order/prescription/components/source.vue b/apps/web-antd/src/views/business/order/prescription/components/source.vue
index a05c8d9b..2cc55f84 100644
--- a/apps/web-antd/src/views/business/order/prescription/components/source.vue
+++ b/apps/web-antd/src/views/business/order/prescription/components/source.vue
@@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
-
+
diff --git a/apps/web-antd/src/views/business/order/product-order/api/index.ts b/apps/web-antd/src/views/business/order/product-order/api/index.ts
index f45b2456..af56d78d 100644
--- a/apps/web-antd/src/views/business/order/product-order/api/index.ts
+++ b/apps/web-antd/src/views/business/order/product-order/api/index.ts
@@ -79,6 +79,18 @@ export async function expressDetailByOrderId(data: Record) {
return requestClient.post(`express-detail/detail-by-order`, data);
}
+/**
+ * 修改已发货运单:快递单号 / 查件手机号 / 快递公司
+ */
+export async function updateExpressNosApi(data: {
+ express_no_id: number;
+ express_no?: string;
+ mobile?: string;
+ express_company_code?: string;
+}) {
+ return requestClient.post(`express-detail/update-express-nos`, data);
+}
+
/**
* 超管:将历史订单级运单同步到分包裹 shipment
*/
diff --git a/apps/web-antd/src/views/business/order/product-order/components/detail.vue b/apps/web-antd/src/views/business/order/product-order/components/detail.vue
index 1fd244e1..b7d47b72 100644
--- a/apps/web-antd/src/views/business/order/product-order/components/detail.vue
+++ b/apps/web-antd/src/views/business/order/product-order/components/detail.vue
@@ -188,6 +188,20 @@ function packageTabLabel(pkg: Record, idx: number): string {
return name ? `包裹${no}(${name})` : `包裹${no}`;
}
+/**
+ * 收件人姓名脱敏:保留首字,其余用 *(与物流动态弹窗一致)
+ */
+function maskExpressName(name: unknown): string {
+ const str = String(name || '').trim();
+ if (!str) {
+ return '-';
+ }
+ if (str.length === 1) {
+ return `${str}*`;
+ }
+ return str.slice(0, 1) + '*'.repeat(str.length - 1);
+}
+
/**
* 打开独立物流动态弹窗(完整轨迹)
*/
@@ -249,7 +263,7 @@ function prescriptionStatusColor() {
}
-
+