feat: 会员管理、在线复诊优化、新增配送仓库

This commit is contained in:
李琦
2026-07-23 13:31:32 +08:00
parent dabfa45ac0
commit cf3fbf5a4c
14 changed files with 1030 additions and 376 deletions

View File

@@ -1,5 +1,6 @@
<template>
<view class="safe-area-inset-bottom page-bg">
<!-- page-flex整页纵向 flexswiper 吃满剩余高度避免死高度裁切 -->
<view class="safe-area-inset-bottom page-bg page-flex">
<!-- #ifdef MP-WEIXIN -->
<page-container
:if="pageBackGuardShow"
@@ -8,36 +9,29 @@
/>
<!-- #endif -->
<!-- 导航栏 -->
<u-navbar class="navbar" :is-back="true" :title="navbarTitle" :custom-back="boundCustomBack" title-color="#000" background="{ background: '#fff' }">
<u-navbar class="navbar rx-flex-fixed" :is-back="true" :title="navbarTitle" :custom-back="boundCustomBack" title-color="#000" background="{ background: '#fff' }">
</u-navbar>
<!-- 处方类型Tab切换 -->
<view v-if="!isSalespersonTransferMode" class="tabs flex p-32 flex-ali-center flex-jus-sp shadow-sm" style="overflow-x: auto; position: relative; z-index: 10;">
<u-button
:throttle-time="0"
:plain="true"
:type="activeCategory === category.value ? 'success' : 'default'"
v-for="(category, index) in prescriptionCategories"
:key="category.value"
:custom-style="tabsBtnStyle"
@click="handleSwitchCategory(category.value)"
:hairline="true"
:hoverStayTime="0"
:style="{
display: (index === 0 && identity === 2) || identity === 1 ? 'block' : 'none',
marginLeft: '10rpx',
}">
{{ category.label }}
</u-button>
<!-- 处方类型 Tab与用户端订单列表一致u-tabs + 下方 swiper 双向同步 -->
<view v-if="!isSalespersonTransferMode" class="tabs-wrap shadow-sm rx-flex-fixed">
<u-tabs
:list="prescriptionTabsList"
:is-scroll="true"
:current="swiperCurrent"
active-color="#6ACDBB"
inactive-color="#606266"
:bold="true"
@change="onRxTabChange"
></u-tabs>
</view>
<view v-if="!isSalespersonTransferMode && registerModeHint" class="register-mode-hint mx-32 m-t-12">
<view v-if="!isSalespersonTransferMode && registerModeHint" class="register-mode-hint mx-32 m-t-12 rx-flex-fixed">
<text class="register-mode-hint__text">{{ registerModeHint }}</text>
</view>
<view
v-if="!isSalespersonTransferMode && hasSpecialPrescriptionRecord"
class="special-rx-card mx-32 m-t-12"
class="special-rx-card mx-32 m-t-12 rx-flex-fixed"
>
<view class="special-rx-card__inner flex-row flex-ali-center">
<u-image
@@ -87,17 +81,17 @@
</view>
</view>
<view v-if="!isSalespersonTransferMode && allowInsuranceCategory" class="mx-32 m-t-12 flex flex-ali-center">
<view v-if="!isSalespersonTransferMode && allowInsuranceCategory" class="mx-32 m-t-12 flex flex-ali-center rx-flex-fixed">
<u-button size="mini" :type="feeCategory === 1 ? 'success' : 'default'" @click="feeCategory = 1">自费</u-button>
<u-button size="mini" class="m-l-16" :type="feeCategory === 2 ? 'success' : 'default'" @click="feeCategory = 2">医保</u-button>
</view>
<view v-if="!isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12">
<view v-if="!isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12 rx-flex-fixed">
<u-button size="mini" type="primary" plain @click="openSalespersonTransfer">查看传方</u-button>
</view>
<!-- 诊断输入区域传方模式不展示 -->
<view v-if="!isSalespersonTransferMode" class="top white p-32 flex-col m-t-16 radius-12 mx-32">
<view v-if="!isSalespersonTransferMode" class="top white p-32 flex-col m-t-16 radius-12 mx-32 rx-flex-fixed">
<view class="top_title flex-row flex-ali-center">
<image :src="require('@/static/image/js.png')" class="size-32"></image>
<d-text text="临床诊断" className="fs-32 m-l-16 font-bold" color="#333"></d-text>
@@ -124,8 +118,19 @@
</view>
</view>
<!-- 滚动内容区域 -->
<scroll-view :style="scrollStyle" scroll-y="true">
<!-- 滚动内容swiper flex:1 吃满剩余高度对齐订单列表 order-swiper不算死高度 -->
<swiper
class="rx-order-swiper"
:current="swiperCurrent"
:disable-touch="isSalespersonTransferMode || swiperCategories.length < 2"
@change="onRxSwiperChange"
>
<swiper-item
v-for="(cat, idx) in swiperCategories"
:key="cat.value"
>
<scroll-view scroll-y class="rx-order-scroll">
<block v-if="swiperCurrent === idx">
<!-- 在线复诊 + 中药证候/治法/中医疾病接口 diseases_idmethod_idsyndrome_id PC 在线问诊处方一致 -->
<view v-if="activeCategory === 1 && isOnlineRevisit" class="white p-32 radius-12 mx-32 m-t-24 flex-col">
@@ -249,7 +254,7 @@
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-row flex-ali-center">
<view class="rp-icon">Rp</view>
<text class="fs-28 text-gray m-l-8">中药处方</text>
<text class="fs-28 text-gray m-l-8">中药处方已选 {{ currentDrugs.length }} </text>
</view>
<view class="flex-row" style="height: 50rpx;">
<u-button
@@ -280,7 +285,7 @@
<view class="white radius-12 p-32" v-if="currentDrugs.length > 0">
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-col">
<text class="fs-28 font-bold color-title">药材清单 ({{currentDrugs.length}})</text>
<text class="fs-28 font-bold color-title">药材清单{{currentDrugs.length}}</text>
<text
v-if="!isSpecialPrescriptionCartLocked"
class="fs-22 text-gray m-t-4"
@@ -328,7 +333,8 @@
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
<view class="flex-row flex-ali-center">
<view class="rp-icon">Rp</view>
<text class="fs-28 text-gray m-l-8">产品/器械</text>
<!-- 与当前 Tab 名一致避免 3/5/6/7 共用模板时写死产品/器械 -->
<text class="fs-28 text-gray m-l-8">{{ activeCategoryLabel }}</text>
</view>
<view class="flex-row m-l-163" style="height: 50rpx;">
<view v-if="!isSpecialPrescriptionCartLocked" class="m-l-16">
@@ -482,7 +488,11 @@
<u-icon name="shield-check" color="#B0B6C2" size="28" class="m-r-8"></u-icon>
<text class="fs-24 color-sub">请确认患者已在实体医院就诊并有明确诊断</text>
</view>
</scroll-view>
</block>
<view v-else class="rx-swiper-placeholder"></view>
</scroll-view>
</swiper-item>
</swiper>
<!-- 底部操作栏 -->
<view class="bottom flex-jus-sp flex-ali-center safe-area-inset-bottom">
@@ -632,6 +642,7 @@ import {
saveWestCommonPrescriptionApi,
saveChineseCommonPrescriptionApi,
getCurrentStoreTypeApi,
getPrescriptionTypeOptionsApi,
getPriceAdjustConfigApi,
getSalespersonTransferByRegisterApi,
getPrescriptionInfoApi,
@@ -711,13 +722,19 @@ export default {
pendingReusePrescriptionId: '',
patientInfo: null,
prescriptionCategories: [
{ label: '中药', value: 1 },
{ label: '西(中成)药', value: 2 },
{ label: '保健食品', value: 3 },
{ label: '产品服务包', value: 5 },
{ label: '非药品', value: 6 },
{ label: '医疗器械', value: 7 }
{ 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: '' }
],
/** 后端下发的默认处方类型;无本地草稿/锁定时使用 */
prescriptionTypeDefault: 2,
/** swiper / u-tabs 当前下标;默认与 activeCategory=2西药列表第 2 项)对齐,避免首屏 Tab/内容错位 */
swiperCurrent: 1,
/** 防止 tabs 与 swiper 互相触发形成回路(同订单列表) */
tabChanging: false,
activeCategory: 2,
currentDrugs: [],
diagnoses: [],
@@ -848,6 +865,39 @@ export default {
};
},
computed: {
/** 可见处方类型identity=2 仅中药;其余展示后端下发的全部 */
visiblePrescriptionCategories() {
const list = Array.isArray(this.prescriptionCategories) ? this.prescriptionCategories : [];
if (Number(this.identity) === 2) {
return list.filter((c) => Number(c.value) === 1);
}
return list;
},
/** swiper 用分类:传方模式仅中药一项 */
swiperCategories() {
if (this.isSalespersonTransferMode) {
return [{ label: '中药', value: 1, icon: '', icon_text: '' }];
}
return this.visiblePrescriptionCategories;
},
/** u-tabs 需要 { name };有 icon_text 时拼到 name 后便于看见角标;始终返回数组避免 list 非数组警告 */
prescriptionTabsList() {
const list = this.visiblePrescriptionCategories || [];
return list.map((c) => {
let name = c.label || '';
if (c.icon_text) name = `${name} ${c.icon_text}`;
return { name, value: c.value, icon: c.icon || '' };
}) || [];
},
/**
* 当前处方类型显示名:简单产品列表等共用区块按 Tab label 展示,
* 避免写死「产品/器械」导致保健食品等类型文案不对
*/
activeCategoryLabel() {
const list = this.visiblePrescriptionCategories || this.prescriptionCategories || [];
const hit = list.find((c) => Number(c.value) === Number(this.activeCategory));
return (hit && hit.label) || '产品';
},
allowInsuranceCategory() {
return Number(this.registerStoreInfo?.allow_insurance_category ?? 0) === 1;
},
@@ -965,25 +1015,6 @@ export default {
shouldEnablePageBackGuard() {
return this.hasPrescriptionOverlay;
},
scrollStyle() {
try {
const sys = uni.getSystemInfoSync();
const rpxRatio = sys.windowWidth / 750;
const topRpx = this.isSalespersonTransferMode ? 500 : 600;
const bottomRpx = 200;
const heightPx = sys.windowHeight - topRpx * rpxRatio - bottomRpx * rpxRatio;
return {
height: `${Math.max(Math.floor(heightPx), 200)}px`,
marginBottom: '200rpx',
};
} catch (e) {
const topRpx = this.isSalespersonTransferMode ? 500 : 600;
return {
height: `calc(100vh - ${topRpx}rpx)`,
marginBottom: '200rpx',
};
}
},
onlineTcmDiseasesLabel() {
const data = this.traditionalTcmData;
const id = this.onlineTcmDiseasesId;
@@ -1038,6 +1069,8 @@ export default {
created() {
// u-navbar 在小程序内 bind($parent) 可能拿不到页面实例,用箭头函数固定 this
this.boundCustomBack = () => this.handleCustomBack();
// 按默认 activeCategory 立刻对齐 swiper 下标,避免首屏 Tab 在中药、内容按西药渲染
this.syncSwiperCurrentFromCategory();
},
onLoad(options) {
this.isSalespersonTransferMode = String(options.salesperson_transfer) === '1';
@@ -1114,11 +1147,13 @@ export default {
await this.loadPatientInfo();
await this.loadRegisterOrderType();
await this.loadBasicConfigData();
await this.loadPrescriptionTypeOptions();
await this.loadRegisterStoreInfo();
// 无 URL/上次切换/草稿时,按诊所类型兜底中药或西药 Tab
this.applyDefaultCategoryByClinicType();
// 无 URL/上次切换/草稿时,用后端 default 兜底
this.applyDefaultCategoryFromOptions();
}
this.loadCurrentCategoryDrugs();
this.syncSwiperCurrentFromCategory();
if (this.activeCategory === 1 && this.chineseConfig.ruleType === 2) {
await this.hydrateChineseProcessRuleListsFromConfig();
}
@@ -1177,6 +1212,101 @@ export default {
};
}
},
/**
* 拉取后端处方类型列表与默认选中;失败时保留本地兜底列表
*/
async loadPrescriptionTypeOptions() {
try {
const params = {};
if (this.registerId) params.register_id = this.registerId;
const res = await getPrescriptionTypeOptionsApi(params);
// req 已解包为业务 data{ default, list }
const data = res || {};
const list = Array.isArray(data.list) ? data.list : [];
if (list.length) {
this.prescriptionCategories = list.map((item) => ({
value: Number(item.value),
label: item.label || '',
icon: item.icon || '',
icon_text: item.icon_text || '',
}));
}
const def = Number(data.default);
if (def) this.prescriptionTypeDefault = def;
} catch (e) {
console.error('加载处方类型失败:', e);
}
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 使用后端下发的 default。优先级URL > 上次切换 > 草稿 > 接口 default
*/
applyDefaultCategoryFromOptions() {
if (this.isSalespersonTransferMode || this._categoryLockedByQuery) return;
if (!this.registerId) return;
if (PrescriptionStorage.getActiveCategory(this.registerId)) return;
if (PrescriptionStorage.hasDraftWithDrugs(this.registerId)) return;
const next = Number(this.prescriptionTypeDefault) || 0;
if (!next || next === this.activeCategory) return;
this.activeCategory = next;
PrescriptionStorage.saveActiveCategory(next, this.registerId);
this.focusActiveTab();
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 按诊所类型设中药(1)/西药(2)。优先级URL > 上次切换 > 草稿 > clinic_type
* @deprecated 已由 applyDefaultCategoryFromOptions 替代,保留兼容
*/
applyDefaultCategoryByClinicType() {
this.applyDefaultCategoryFromOptions();
},
/** 将当前 Tab 滚入可视区u-tabs is-scroll 已自动聚焦,此处同步 swiper 下标) */
focusActiveTab() {
this.syncSwiperCurrentFromCategory();
},
/** 根据 activeCategory 同步 swiperCurrent */
syncSwiperCurrentFromCategory() {
const list = this.swiperCategories || [];
const idx = list.findIndex((c) => Number(c.value) === Number(this.activeCategory));
const next = idx >= 0 ? idx : 0;
if (next === this.swiperCurrent) return;
// 程序化改 current 会触发 swiper @change用 tabChanging 挡住回路
this.tabChanging = true;
this.swiperCurrent = next;
this.$nextTick(() => {
this.tabChanging = false;
});
},
/** Tab 点击切换(与订单列表 onTabChange 一致) */
onRxTabChange(index) {
if (this.tabChanging) return;
this.tabChanging = true;
const idx = typeof index === 'object' ? Number(index.index ?? index) : Number(index);
const list = this.swiperCategories || [];
const cat = list[idx];
if (cat) {
this.swiperCurrent = idx;
this.handleSwitchCategory(cat.value, { skipFocus: true });
}
this.$nextTick(() => {
this.tabChanging = false;
});
},
/** swiper 滑动切换 */
onRxSwiperChange(e) {
if (this.tabChanging) return;
this.tabChanging = true;
const idx = Number((e.detail && e.detail.current) || 0);
const list = this.swiperCategories || [];
const cat = list[idx];
if (cat) {
this.swiperCurrent = idx;
this.handleSwitchCategory(cat.value, { skipFocus: true });
}
this.$nextTick(() => {
this.tabChanging = false;
});
},
restoreFromLocalStorage() {
if (this._presetActiveCategory !== null && this._presetActiveCategory !== undefined && !Number.isNaN(this._presetActiveCategory)) {
this.activeCategory = this._presetActiveCategory;
@@ -1198,28 +1328,6 @@ export default {
}
}
},
/**
* 开方默认 Tab 兜底:仅当无 URL 锁定、无上次切换分类、无任何药品草稿时,
* 按诊所类型设中药(1)/西药(2)。优先级URL > 上次切换 > 草稿 > clinic_type
*/
applyDefaultCategoryByClinicType() {
if (this.isSalespersonTransferMode || this._categoryLockedByQuery) return;
if (!this.registerId) return;
// 最后切换的分类优先,不再用诊所类型覆盖
if (PrescriptionStorage.getActiveCategory(this.registerId)) return;
// 任一类有草稿则沿用 restore 结果
if (PrescriptionStorage.hasDraftWithDrugs(this.registerId)) return;
const clinicType = Number(this.registerStoreInfo?.clinic_type ?? 0);
let next = 0;
if (clinicType === 2) {
next = 1; // 中医诊所 → 中药
} else if (clinicType === 1) {
next = 2; // 西医诊所 → 西(中成)药
}
if (!next || next === this.activeCategory) return;
this.activeCategory = next;
PrescriptionStorage.saveActiveCategory(next, this.registerId);
},
loadCurrentCategoryDrugs() {
const data = PrescriptionStorage.loadPrescriptionData(this.activeCategory, this.registerId);
if (data) {
@@ -1501,10 +1609,15 @@ export default {
this.$toast('加载传方失败');
}
},
async handleSwitchCategory(category) {
async handleSwitchCategory(category, options = {}) {
this.saveToLocalStorage();
this.activeCategory = category;
this.loadCurrentCategoryDrugs();
if (!options.skipFocus) {
this.focusActiveTab();
} else {
this.syncSwiperCurrentFromCategory();
}
if (category === 1) {
await this.checkSalespersonTransfer();
await this.checkAndShowTransferTip();
@@ -2736,7 +2849,17 @@ export default {
/* 基础与通用样式 */
.page-bg {
background-color: #F4F6F8;
min-height: 100vh;
}
/* 整页纵向 flex顶部 chrome 不收缩swiper 吃满剩余高度 */
.page-flex {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
}
.rx-flex-fixed {
flex-shrink: 0;
}
.safe-area-inset-bottom { padding-bottom: env(safe-area-inset-bottom); }
.mx-32 { margin-left: 32rpx; margin-right: 32rpx; }
@@ -2812,9 +2935,26 @@ export default {
border-radius: 6rpx 2rpx 6rpx 2rpx;
}
.tabs {
.tabs-wrap {
background: #fff;
border-bottom: 1px solid #F0F2F5;
position: relative;
z-index: 10;
}
.rx-order-swiper {
flex: 1;
height: 0;
min-height: 0;
width: 100%;
}
/* 内部滚动区铺满 swiper底部留白避开 fixed 操作栏 */
.rx-order-scroll {
height: 100%;
box-sizing: border-box;
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
}
.rx-swiper-placeholder {
min-height: 400rpx;
}
/* 现代卡片设计 (西药 / 产品) */