diff --git a/api/businessApi.js b/api/businessApi.js
index 1367574..48892d7 100644
--- a/api/businessApi.js
+++ b/api/businessApi.js
@@ -88,6 +88,10 @@ export function createBusinessApi(prefix, headerFlag) {
getOrderStatusOption() {
return bizRequest({ url: `${prefix}-order/status-option`, method: 'GET' });
},
+ /** 各状态订单数量(筛选不含 status,供 Tab 徽标) */
+ getOrderStatusCount(params) {
+ return bizRequest({ url: `${prefix}-order/status-count`, method: 'GET', data: params });
+ },
getOrderDetail(id) {
return bizRequest({ url: `${prefix}-order/detail`, method: 'GET', data: { id } });
},
diff --git a/api/clinicAdmin.js b/api/clinicAdmin.js
index 88dac30..aef3b9e 100644
--- a/api/clinicAdmin.js
+++ b/api/clinicAdmin.js
@@ -127,6 +127,11 @@ export function getOrderStatusOption() {
return clinicRequest({ url: `${PREFIX}-order/status-option`, method: 'GET' });
}
+/** 各状态订单数量(筛选不含 status,供 Tab 徽标) */
+export function getOrderStatusCount(params) {
+ return clinicRequest({ url: `${PREFIX}-order/status-count`, method: 'GET', data: params });
+}
+
export function getOrderDetail(id) {
return clinicRequest({ url: `${PREFIX}-order/detail`, method: 'GET', data: { id } });
}
diff --git a/api/reception.js b/api/reception.js
index f999c6c..67f29c6 100644
--- a/api/reception.js
+++ b/api/reception.js
@@ -344,6 +344,18 @@ export function getCurrentStoreTypeApi(data) {
})
}
+/**
+ * 开方处方类型选项(含默认选中、可选 icon / icon_text)
+ * 后端按 clinic_type 判断 default;未传 icon 时前端不展示
+ */
+export function getPrescriptionTypeOptionsApi(data) {
+ return req.request({
+ url: '/newApi/doctor-reception-wx/prescription-type-options',
+ method: 'GET',
+ data: data || {}
+ })
+}
+
// 订单百分比调价配置(开方前)
export function getPriceAdjustConfigApi(storeId) {
return req.request({
diff --git a/pages.json b/pages.json
index 72f3e25..c755f7f 100644
--- a/pages.json
+++ b/pages.json
@@ -172,7 +172,13 @@
"path": "prescription_v2/index",
"style": {
"navigationBarTitleText": "开方",
- "navigationStyle": "custom"
+ "navigationStyle": "custom",
+ "usingComponents": {
+ "order-price-percent-adjust-popup": "/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup"
+ },
+ "componentPlaceholder": {
+ "order-price-percent-adjust-popup": "view"
+ }
}
},
{
@@ -437,7 +443,11 @@
},
{
"path": "order/index",
- "style": { "navigationBarTitleText": "商品订单", "navigationStyle": "custom" }
+ "style": {
+ "navigationBarTitleText": "商品订单",
+ "navigationStyle": "custom",
+ "disableScroll": true
+ }
},
{
"path": "order/detail",
@@ -468,7 +478,7 @@
}, {
"root": "subPackages/sub_business_shared",
"pages": [
- { "path": "order/index", "style": { "navigationBarTitleText": "商品订单", "navigationStyle": "custom" } },
+ { "path": "order/index", "style": { "navigationBarTitleText": "商品订单", "navigationStyle": "custom", "disableScroll": true } },
{ "path": "order/detail", "style": { "navigationBarTitleText": "订单详情", "navigationStyle": "custom" } },
{ "path": "order/trace/index", "style": { "navigationBarTitleText": "订单追溯", "navigationStyle": "custom" } },
{ "path": "reconciliation/index", "style": { "navigationBarTitleText": "对账单", "navigationStyle": "custom" } },
diff --git a/subPackages/sub_business_shared/common/display.js b/subPackages/sub_business_shared/common/display.js
index 3678ec2..7bde348 100644
--- a/subPackages/sub_business_shared/common/display.js
+++ b/subPackages/sub_business_shared/common/display.js
@@ -226,6 +226,46 @@ export function mapOrderListRow(item) {
}
}
+/**
+ * 列表卡片瘦字段:只保留 UI 需要的标量,丢弃处方 content / 明细整包,
+ * 避免微信小程序 setData 体积过大(曾出现 ~1.5MB 告警)
+ * @param {Object} raw 接口订单行
+ * @returns {Object}
+ */
+export function toOrderCardRow(raw) {
+ if (!raw) return { _wxKey: '0', id: 0 }
+ const d = mapOrderListRow(raw)
+ const id = raw.id
+ const status = raw.status
+ // 样式用:已完成/取消给语义 class,其余用状态码
+ let statusKey = status != null ? String(status) : 'default'
+ if (status === 6 || status === 7) statusKey = 'done'
+ else if (status === 9) statusKey = 'cancel'
+ const payText = (d.totalPayPrice || '').replace('¥', '').trim()
+ return {
+ _wxKey: id != null && id !== '' ? String(id) : 'ord0',
+ id,
+ status,
+ statusKey,
+ orderNo: d.orderNo,
+ storeName: d.storeName,
+ statusText: d.statusText,
+ productSummary: d.productSummary || '药品订单',
+ prescriptionTypeText: d.prescriptionTypeText,
+ deliveryText: d.deliveryText,
+ patientName: d.patientName,
+ userNickname: d.userNickname,
+ receiverName: d.receiverName,
+ receiverMobile: d.receiverMobile,
+ createdAt: d.createdAt,
+ totalPayPrice: d.totalPayPrice,
+ payAmountText: payText,
+ pId: d.pId,
+ canViewPrescription: d.canViewPrescription,
+ upId: d.upId,
+ }
+}
+
/** 物流详情(对齐 PC express-detail/detail-by-order) */
export function mapExpressDetail(data) {
if (!data || typeof data !== 'object') {
diff --git a/subPackages/sub_business_shared/common/orderParams.js b/subPackages/sub_business_shared/common/orderParams.js
index 04daa6c..9906817 100644
--- a/subPackages/sub_business_shared/common/orderParams.js
+++ b/subPackages/sub_business_shared/common/orderParams.js
@@ -30,3 +30,15 @@ export function buildOrderSaleAmountParams(opts) {
const { page, pageSize, ...rest } = opts || {}
return buildOrderListParams(rest)
}
+
+/**
+ * 状态数量接口参数:与列表筛选一致,但不传 status
+ * (徽标要展示每个状态的数量,不能被当前 Tab 的 status 过滤)
+ */
+export function buildOrderStatusCountParams(opts) {
+ const params = buildOrderSaleAmountParams(opts)
+ if (params && Object.prototype.hasOwnProperty.call(params, 'status')) {
+ delete params.status
+ }
+ return params
+}
diff --git a/subPackages/sub_business_shared/components/ListSkeleton.vue b/subPackages/sub_business_shared/components/ListSkeleton.vue
new file mode 100644
index 0000000..68a7d6d
--- /dev/null
+++ b/subPackages/sub_business_shared/components/ListSkeleton.vue
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_business_shared/order/index.vue b/subPackages/sub_business_shared/order/index.vue
index 180028f..707ec12 100644
--- a/subPackages/sub_business_shared/order/index.vue
+++ b/subPackages/sub_business_shared/order/index.vue
@@ -1,12 +1,26 @@
-
-
-
-
-
s.label || s.name || '全部')
+ /** u-tabs:name + count(0 不显示徽标) */
+ statusTabsList() {
+ return (this.statusOptions || []).map((s) => {
+ const count = Number(s.count) > 0 ? Number(s.count) : 0
+ return {
+ name: s.label || s.name || '全部',
+ count,
+ }
+ }) || []
},
deliveryLabels() {
return DELIVERY_OPTIONS.map(o => o.label)
@@ -222,8 +262,7 @@ export default {
},
},
created() {
- // 动态计算顶部 u-navbar 的总高度 (状态栏 + navbar 固定 44px 高度)
- // 这一步与 business-page-layout 内的计算逻辑保持完全一致,保证完美对接
+ // 仅用于 page-flex 高度 calc;layout 已 paddingTop,勿再绑到 header 的 relative top
const sys = uni.getSystemInfoSync()
const statusBarHeight = this.$statusBarHeight || sys.statusBarHeight || 0
const navbarHeight = this.$navbarHeight || 44
@@ -234,26 +273,24 @@ export default {
this.bizApi.getOrderStatusOption().then(w => {
const opts = (w.data && (w.data.items || w.data)) || []
if (Array.isArray(opts) && opts.length) {
- this.statusOptions = [{ label: '全部', value: '' }].concat(opts.map(o => ({
+ this.statusOptions = [{ label: '全部', value: '', count: 0 }].concat(opts.map(o => ({
label: o.label || o.name,
value: o.value != null ? o.value : o.id,
+ count: 0,
})))
+ if (this.statusIndex >= this.statusOptions.length) this.statusIndex = 0
}
+ this.loadStatusCount()
})
this.checkDateEndAndReload()
},
- /** 离开/隐藏时关掉处方抽屉,避免 page-container 残留白屏 */
onHide() {
this.closePrescriptionPopup()
},
onUnload() {
this.closePrescriptionPopup()
},
- onReachBottom() {
- this.load(this.page + 1, true)
- },
methods: {
- /** 关闭处方抽屉 */
closePrescriptionPopup() {
const pop = this.$refs.prescriptionPopup
if (pop && typeof pop.onClose === 'function') {
@@ -314,14 +351,14 @@ export default {
dateEnd: this.dateEnd,
}
},
- rowDisplay(item) {
- return mapOrderListRow(item)
- },
+ /** 重新加载列表、销售金额与状态徽标 */
reload() {
this.persistOrderFilter()
this.page = 1
+ this.hasMore = true
this.load(1, false)
this.loadSaleAmount()
+ this.loadStatusCount()
},
loadSaleAmount() {
const params = buildOrderSaleAmountParams(this.filterValues())
@@ -337,21 +374,83 @@ export default {
]
})
},
+ /**
+ * 拉各状态数量写回 statusOptions.count(不含当前 status 筛选)
+ */
+ loadStatusCount() {
+ const params = buildOrderStatusCountParams(this.filterValues())
+ this.bizApi.getOrderStatusCount(params).then((w) => {
+ if (!w.ok || !w.data) return
+ const total = Number(w.data.total || 0)
+ const map = {}
+ const items = w.data.items || []
+ items.forEach((it) => {
+ map[Number(it.id)] = Number(it.count || 0)
+ })
+ this.statusOptions = (this.statusOptions || []).map((s, i) => {
+ const isAll = i === 0 || s.value === '' || s.value == null
+ return {
+ ...s,
+ count: isAll ? total : (map[Number(s.value)] || 0),
+ }
+ })
+ })
+ },
load(page, append) {
this.loading = true
+ const pageSize = this.pageSize || 15
const params = buildOrderListParams({
page,
- pageSize: 15,
+ pageSize,
...this.filterValues(),
})
this.bizApi.getOrderList(params).then(w => {
- const items = withWxKey((w.data && w.data.items) || [], 'id', 'ord')
+ // 只写入卡片瘦字段,避免把处方 content / 明细整包 setData
+ const rawItems = (w.data && w.data.items) || []
+ const items = rawItems.map((raw) => toOrderCardRow(raw))
this.list = append ? this.list.concat(items) : items
this.page = page
- }).finally(() => { this.loading = false })
+ this.hasMore = rawItems.length >= pageSize
+ }).finally(() => {
+ this.loading = false
+ if (this.isRefreshing) this.isRefreshing = false
+ })
},
- onStatus(e) {
- this.statusIndex = Number(e.detail.value)
+ /** scroll-view 下拉刷新 */
+ onRefresherRefresh() {
+ this.isRefreshing = true
+ this.reload()
+ },
+ /** scroll-view 触底分页 */
+ onScrollToLower() {
+ if (this.loading || !this.hasMore) return
+ this.load(this.page + 1, true)
+ },
+ /** 状态 Tab 点击:与 swiper 双向同步 */
+ onStatusTabChange(index) {
+ if (this.tabChanging) return
+ this.tabChanging = true
+ const idx = typeof index === 'object' ? Number(index.index != null ? index.index : index) : Number(index)
+ if (!Number.isNaN(idx) && idx !== this.statusIndex) {
+ this.statusIndex = idx
+ this.reload()
+ }
+ this.$nextTick(() => {
+ this.tabChanging = false
+ })
+ },
+ /** 左右滑切换状态 */
+ onStatusSwiperChange(e) {
+ if (this.tabChanging) return
+ this.tabChanging = true
+ const idx = Number((e.detail && e.detail.current) || 0)
+ if (idx !== this.statusIndex) {
+ this.statusIndex = idx
+ this.reload()
+ }
+ this.$nextTick(() => {
+ this.tabChanging = false
+ })
},
onDelivery(e) {
this.deliveryIndex = Number(e.detail.value)
@@ -368,16 +467,14 @@ export default {
if (!pId) return
this.$refs.prescriptionPopup.open(pId)
},
- /** 打开患者视角详情抽屉 */
openPatient(item) {
- const d = this.rowDisplay(item)
- const upId = Number(d.upId || 0)
+ const upId = Number(item.upId || 0)
if (!upId) {
uni.showToast({ title: '缺少就诊人信息', icon: 'none' })
return
}
this.patientDetailUpId = upId
- this.patientDetailName = d.patientName !== '-' ? d.patientName : ''
+ this.patientDetailName = item.patientName !== '-' ? item.patientName : ''
this.patientDetailVisible = true
},
},
@@ -394,30 +491,58 @@ $color-border: #E5E6EB;
.order-list-container {
background-color: $color-bg-base;
- min-height: 100vh;
- padding-bottom: 60rpx;
+ height: 100%;
+ box-sizing: border-box;
+}
+.page-flex {
+ /* height 由行内 calc(100vh - navTopOffset) 控制,避免再叠 layout 的 paddingTop */
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ min-height: 0;
+ box-sizing: border-box;
+}
+.rx-flex-fixed {
+ flex-shrink: 0;
+}
+.order-swiper {
+ flex: 1;
+ height: 0;
+ min-height: 0;
+ width: 100%;
+}
+.order-scroll {
+ height: 100%;
+ box-sizing: border-box;
+}
+.rx-swiper-placeholder {
+ min-height: 200rpx;
}
/* ================= 核心:完美吸顶方案 ================= */
-/* 原生 sticky 结合 Vue 计算出的精确导航栏高度(top),严丝合缝拦截在导航栏正下方 */
.native-sticky-header {
- position: -webkit-sticky;
- position: sticky;
- z-index: 100; /* 高层级,保证滑动时覆盖在下方的卡片上 */
- background-color: $color-bg-base; /* 必须有底色用于防穿透遮挡 */
+ position: relative;
+ z-index: 100;
+ background-color: $color-bg-base;
}
.header-solid-bg {
/* 使用 padding 将内容向内挤,防止滑动过程的透明间隙穿透问题 */
padding: 16rpx 24rpx 16rpx;
}
-
+.tabs-wrap {
+ background: #fff;
+ border-radius: 20rpx 20rpx 0 0;
+ margin-bottom: 0;
+ overflow: hidden;
+}
/* 筛选项 UI 美化 */
.quick-filter {
background: #fff;
padding: 16rpx 20rpx;
- border-radius: 20rpx;
+ border-radius: 0 0 20rpx 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+ margin-bottom: 16rpx;
}
.filter-row {
display: flex;
diff --git a/subPackages/sub_clinic_admin/common/display.js b/subPackages/sub_clinic_admin/common/display.js
index 373e475..c29afba 100644
--- a/subPackages/sub_clinic_admin/common/display.js
+++ b/subPackages/sub_clinic_admin/common/display.js
@@ -226,6 +226,45 @@ export function mapOrderListRow(item) {
}
}
+/**
+ * 列表卡片瘦字段:只保留 UI 需要的标量,丢弃处方 content / 明细整包,
+ * 避免微信小程序 setData 体积过大
+ * @param {Object} raw 接口订单行
+ * @returns {Object}
+ */
+export function toOrderCardRow(raw) {
+ if (!raw) return { _wxKey: '0', id: 0 }
+ const d = mapOrderListRow(raw)
+ const id = raw.id
+ const status = raw.status
+ let statusKey = status != null ? String(status) : 'default'
+ if (status === 6 || status === 7) statusKey = 'done'
+ else if (status === 9) statusKey = 'cancel'
+ const payText = (d.totalPayPrice || '').replace('¥', '').trim()
+ return {
+ _wxKey: id != null && id !== '' ? String(id) : 'ord0',
+ id,
+ status,
+ statusKey,
+ orderNo: d.orderNo,
+ storeName: d.storeName,
+ statusText: d.statusText,
+ productSummary: d.productSummary || '药品订单',
+ prescriptionTypeText: d.prescriptionTypeText,
+ deliveryText: d.deliveryText,
+ patientName: d.patientName,
+ userNickname: d.userNickname,
+ receiverName: d.receiverName,
+ receiverMobile: d.receiverMobile,
+ createdAt: d.createdAt,
+ totalPayPrice: d.totalPayPrice,
+ payAmountText: payText,
+ pId: d.pId,
+ canViewPrescription: d.canViewPrescription,
+ upId: d.upId,
+ }
+}
+
/** 物流详情(对齐 PC express-detail/detail-by-order) */
export function mapExpressDetail(data) {
if (!data || typeof data !== 'object') {
diff --git a/subPackages/sub_clinic_admin/common/orderParams.js b/subPackages/sub_clinic_admin/common/orderParams.js
index 04daa6c..f1d32ac 100644
--- a/subPackages/sub_clinic_admin/common/orderParams.js
+++ b/subPackages/sub_clinic_admin/common/orderParams.js
@@ -30,3 +30,14 @@ export function buildOrderSaleAmountParams(opts) {
const { page, pageSize, ...rest } = opts || {}
return buildOrderListParams(rest)
}
+
+/**
+ * 状态数量接口参数:与列表筛选一致,但不传 status
+ */
+export function buildOrderStatusCountParams(opts) {
+ const params = buildOrderSaleAmountParams(opts)
+ if (params && Object.prototype.hasOwnProperty.call(params, 'status')) {
+ delete params.status
+ }
+ return params
+}
diff --git a/subPackages/sub_clinic_admin/order/index.vue b/subPackages/sub_clinic_admin/order/index.vue
index 6241099..c020707 100644
--- a/subPackages/sub_clinic_admin/order/index.vue
+++ b/subPackages/sub_clinic_admin/order/index.vue
@@ -1,10 +1,25 @@
-
-
-
import ClinicPageLayout from '@/subPackages/sub_clinic_admin/components/clinic-page-layout.vue'
-import PageStickyHeader from '@/subPackages/sub_clinic_admin/components/PageStickyHeader.vue'
import CollapsibleFilterPanel from '@/subPackages/sub_clinic_admin/components/CollapsibleFilterPanel.vue'
+import ListSkeleton from '@/subPackages/sub_business_shared/components/ListSkeleton.vue'
import PrescriptionDetailPopup from './components/PrescriptionDetailPopup.vue'
import UserPatientDetail from '@/subPackages/sub_business_shared/components/UserPatientDetail.vue'
-import { getOrderList, getOrderStatusOption, getOrderSaleAmount } from '@/api/clinicAdmin.js'
-import { withWxKey } from '@/utils/wxListKey.js'
-import { mapOrderListRow } from '@/subPackages/sub_clinic_admin/common/display.js'
+import {
+ getOrderList,
+ getOrderStatusOption,
+ getOrderSaleAmount,
+ getOrderStatusCount,
+} from '@/api/clinicAdmin.js'
+import { toOrderCardRow } from '@/subPackages/sub_clinic_admin/common/display.js'
import { formatMoney } from '@/subPackages/sub_clinic_admin/common/format.js'
import {
buildOrderListParams,
buildOrderSaleAmountParams,
+ buildOrderStatusCountParams,
} from '@/subPackages/sub_clinic_admin/common/orderParams.js'
import {
loadOrderFilter,
@@ -188,28 +216,45 @@ const PRESCRIPTION_TYPE_OPTIONS = [
]
export default {
- components: { ClinicPageLayout, PageStickyHeader, CollapsibleFilterPanel, PrescriptionDetailPopup, UserPatientDetail },
+ components: {
+ ClinicPageLayout,
+ CollapsibleFilterPanel,
+ ListSkeleton,
+ PrescriptionDetailPopup,
+ UserPatientDetail,
+ },
data() {
return {
list: [],
page: 1,
+ pageSize: 15,
loading: false,
+ hasMore: true,
+ isRefreshing: false,
+ tabChanging: false,
orderNo: '',
- statusOptions: [{ label: '全部', value: '' }],
+ statusOptions: [{ label: '全部', value: '', count: 0 }],
statusIndex: 0,
deliveryIndex: 0,
prescriptionTypeIndex: 0,
dateStart: '',
dateEnd: '',
statItems: [],
+ navTopOffset: 0,
patientDetailVisible: false,
patientDetailUpId: 0,
patientDetailName: '',
}
},
computed: {
- statusLabels() {
- return this.statusOptions.map(s => s.label || s.name || '全部')
+ statusTabsList() {
+ return (this.statusOptions || []).map((s) => {
+ const count = Number(s.count) > 0 ? Number(s.count) : 0
+ return {
+ name: s.label || s.name || '全部',
+ count,
+ }
+ }) || []
},
deliveryLabels() {
return DELIVERY_OPTIONS.map(o => o.label)
@@ -218,31 +263,36 @@ export default {
return PRESCRIPTION_TYPE_OPTIONS.map(o => o.label)
},
},
+ created() {
+ // 仅用于 page-flex 高度 calc;layout 已 paddingTop,勿再绑到 header 的 relative top
+ const sys = uni.getSystemInfoSync()
+ const statusBarHeight = this.$statusBarHeight || sys.statusBarHeight || 0
+ const navbarHeight = this.$navbarHeight || 44
+ this.navTopOffset = statusBarHeight + navbarHeight
+ },
onLoad() {
this.applyOrderFilter(loadOrderFilter())
getOrderStatusOption().then(w => {
const opts = (w.data && (w.data.items || w.data)) || []
if (Array.isArray(opts) && opts.length) {
- this.statusOptions = [{ label: '全部', value: '' }].concat(opts.map(o => ({
+ this.statusOptions = [{ label: '全部', value: '', count: 0 }].concat(opts.map(o => ({
label: o.label || o.name,
value: o.value != null ? o.value : o.id,
+ count: 0,
})))
+ if (this.statusIndex >= this.statusOptions.length) this.statusIndex = 0
}
+ this.loadStatusCount()
})
this.checkDateEndAndReload()
},
- /** 离开/隐藏时关掉处方抽屉,避免 page-container 残留白屏 */
onHide() {
this.closePrescriptionPopup()
},
onUnload() {
this.closePrescriptionPopup()
},
- onReachBottom() {
- this.load(this.page + 1, true)
- },
methods: {
- /** 关闭处方抽屉 */
closePrescriptionPopup() {
const pop = this.$refs.prescriptionPopup
if (pop && typeof pop.onClose === 'function') {
@@ -303,14 +353,13 @@ export default {
dateEnd: this.dateEnd,
}
},
- rowDisplay(item) {
- return mapOrderListRow(item)
- },
reload() {
this.persistOrderFilter()
this.page = 1
+ this.hasMore = true
this.load(1, false)
this.loadSaleAmount()
+ this.loadStatusCount()
},
loadSaleAmount() {
const params = buildOrderSaleAmountParams(this.filterValues())
@@ -326,21 +375,76 @@ export default {
]
})
},
+ loadStatusCount() {
+ const params = buildOrderStatusCountParams(this.filterValues())
+ getOrderStatusCount(params).then((w) => {
+ if (!w.ok || !w.data) return
+ const total = Number(w.data.total || 0)
+ const map = {}
+ const items = w.data.items || []
+ items.forEach((it) => {
+ map[Number(it.id)] = Number(it.count || 0)
+ })
+ this.statusOptions = (this.statusOptions || []).map((s, i) => {
+ const isAll = i === 0 || s.value === '' || s.value == null
+ return {
+ ...s,
+ count: isAll ? total : (map[Number(s.value)] || 0),
+ }
+ })
+ })
+ },
load(page, append) {
this.loading = true
+ const pageSize = this.pageSize || 15
const params = buildOrderListParams({
page,
- pageSize: 15,
+ pageSize,
...this.filterValues(),
})
getOrderList(params).then(w => {
- const items = withWxKey((w.data && w.data.items) || [], 'id', 'ord')
+ // 只写入卡片瘦字段,避免把处方 content / 明细整包 setData
+ const rawItems = (w.data && w.data.items) || []
+ const items = rawItems.map((raw) => toOrderCardRow(raw))
this.list = append ? this.list.concat(items) : items
this.page = page
- }).finally(() => { this.loading = false })
+ this.hasMore = rawItems.length >= pageSize
+ }).finally(() => {
+ this.loading = false
+ if (this.isRefreshing) this.isRefreshing = false
+ })
},
- onStatus(e) {
- this.statusIndex = Number(e.detail.value)
+ onRefresherRefresh() {
+ this.isRefreshing = true
+ this.reload()
+ },
+ onScrollToLower() {
+ if (this.loading || !this.hasMore) return
+ this.load(this.page + 1, true)
+ },
+ onStatusTabChange(index) {
+ if (this.tabChanging) return
+ this.tabChanging = true
+ const idx = typeof index === 'object' ? Number(index.index != null ? index.index : index) : Number(index)
+ if (!Number.isNaN(idx) && idx !== this.statusIndex) {
+ this.statusIndex = idx
+ this.reload()
+ }
+ this.$nextTick(() => {
+ this.tabChanging = false
+ })
+ },
+ onStatusSwiperChange(e) {
+ if (this.tabChanging) return
+ this.tabChanging = true
+ const idx = Number((e.detail && e.detail.current) || 0)
+ if (idx !== this.statusIndex) {
+ this.statusIndex = idx
+ this.reload()
+ }
+ this.$nextTick(() => {
+ this.tabChanging = false
+ })
},
onDelivery(e) {
this.deliveryIndex = Number(e.detail.value)
@@ -358,14 +462,13 @@ export default {
this.$refs.prescriptionPopup.open(pId)
},
openPatient(item) {
- const d = this.rowDisplay(item)
- const upId = Number(d.upId || 0)
+ const upId = Number(item.upId || 0)
if (!upId) {
uni.showToast({ title: '缺少就诊人信息', icon: 'none' })
return
}
this.patientDetailUpId = upId
- this.patientDetailName = d.patientName !== '-' ? d.patientName : ''
+ this.patientDetailName = item.patientName !== '-' ? item.patientName : ''
this.patientDetailVisible = true
},
},
@@ -381,16 +484,45 @@ $color-text-muted: #86909C;
.order-list-container {
background-color: $color-bg-base;
- min-height: 100vh;
- padding-bottom: 60rpx;
+ height: 100%;
+ box-sizing: border-box;
+}
+.page-flex {
+ /* height 由行内 calc(100vh - navTopOffset) 控制,避免再叠 layout 的 paddingTop */
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ min-height: 0;
+ box-sizing: border-box;
+}
+.rx-flex-fixed {
+ flex-shrink: 0;
+}
+.order-swiper {
+ flex: 1;
+ height: 0;
+ min-height: 0;
+ width: 100%;
+}
+.order-scroll {
+ height: 100%;
+ box-sizing: border-box;
+}
+.rx-swiper-placeholder {
+ min-height: 200rpx;
}
/* 筛选区 */
+.tabs-wrap {
+ background: #fff;
+ border-radius: 16rpx 16rpx 0 0;
+ overflow: hidden;
+}
.quick-filter {
background: #fff;
padding: 24rpx;
margin-bottom: 16rpx;
- border-radius: 16rpx;
+ border-radius: 0 0 16rpx 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
}
.filter-row {
@@ -657,54 +789,17 @@ $color-text-muted: #86909C;
}
-/* ================= 核心:完美吸顶方案 ================= */
-/* 原生 sticky 结合 Vue 计算出的精确导航栏高度(top),严丝合缝拦截在导航栏正下方 */
+/* ================= 顶部筛选区(flex 布局下不再用 sticky,由 page-flex 固定) ================= */
.native-sticky-header {
- position: -webkit-sticky;
- position: sticky;
- z-index: 100; /* 高层级,保证滑动时覆盖在下方的卡片上 */
- background-color: $color-bg-base; /* 必须有底色用于防穿透遮挡 */
+ position: relative;
+ z-index: 100;
+ background-color: $color-bg-base;
}
.header-solid-bg {
- /* 使用 padding 将内容向内挤,防止滑动过程的透明间隙穿透问题 */
padding: 16rpx 24rpx 16rpx;
}
-/* 筛选项 UI 美化 */
-.quick-filter {
- background: #fff;
- padding: 16rpx 20rpx;
- border-radius: 20rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
-}
-.filter-row {
- display: flex;
- align-items: center;
- gap: 16rpx;
- margin-bottom: 24rpx;
- &:last-child { margin-bottom: 0; }
-}
-.date-row {
- margin-bottom: 0;
- justify-content: space-between;
-}
-.filter-label {
- font-size: 28rpx;
- color: $color-text-body;
- min-width: 130rpx;
-}
-.filter-input, .filter-picker {
- flex: 1;
- font-size: 26rpx;
- padding: 14rpx 20rpx;
- background: #F7F8FA;
- border-radius: 12rpx;
- color: $color-text-main;
-}
-.date-picker { text-align: center; }
-.sep { color: $color-text-muted; font-size: 24rpx; }
-
/* 重新设计的紧凑型查询按钮 */
.btn-query {
flex-shrink: 0;
diff --git a/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue b/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue
index 9064bb0..a2062ce 100644
--- a/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue
+++ b/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue
@@ -11,7 +11,7 @@
>
@@ -286,6 +286,12 @@ export default {
wayPanelDrugIndex: -1
};
},
+ computed: {
+ /** 抽屉标题:选择中药(N种),随 selectedDrugs 动态变更 */
+ modalTitleText() {
+ return '选择中药(' + (this.selectedDrugs || []).length + '种)';
+ },
+ },
watch: {
value(newVal) {
this.show = newVal;
diff --git a/subPackages/sub_workbench/prescription_v2/index.vue b/subPackages/sub_workbench/prescription_v2/index.vue
index 330eb92..73db051 100644
--- a/subPackages/sub_workbench/prescription_v2/index.vue
+++ b/subPackages/sub_workbench/prescription_v2/index.vue
@@ -1,5 +1,6 @@
-
+
+
-
+
-
-
-
- {{ category.label }}
-
+
+
+
-
+
{{ registerModeHint }}
-
+
自费
医保
-
+
查看传方
-
+
@@ -124,8 +118,19 @@
-
-
+
+
+
+
+
@@ -249,7 +254,7 @@
Rp
- 中药处方
+ 中药处方(已选 {{ currentDrugs.length }} 种)
- 药材清单 (共{{currentDrugs.length}}味)
+ 药材清单(共{{currentDrugs.length}}种)
Rp
- 产品/器械
+
+ {{ activeCategoryLabel }}
@@ -482,7 +488,11 @@
请确认患者已在实体医院就诊,并有明确诊断
-
+
+
+
+
+
@@ -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;
}
/* 现代卡片设计 (西药 / 产品) */
diff --git a/subPackages/sub_workbench/prescription_v2/utils/prescriptionStorage.js b/subPackages/sub_workbench/prescription_v2/utils/prescriptionStorage.js
index 5a4c18b..6d54b91 100644
--- a/subPackages/sub_workbench/prescription_v2/utils/prescriptionStorage.js
+++ b/subPackages/sub_workbench/prescription_v2/utils/prescriptionStorage.js
@@ -37,7 +37,6 @@ export class PrescriptionStorage {
try {
const key = this.getStorageKey(category, registerId);
uni.setStorageSync(key, JSON.stringify(data));
- console.log('保存处方数据成功:', key, data);
} catch (error) {
console.error('保存处方数据失败:', error);
}