1075 lines
35 KiB
Vue
1075 lines
35 KiB
Vue
<template>
|
||
<view class="chronic-manage-page">
|
||
<view class="page-hero">
|
||
<AppNavbar title="慢病信息管理" show-back/>
|
||
<view class="intro-card">
|
||
<view class="card-title">
|
||
<view class="title-bar"></view>
|
||
<text>慢病信息管理</text>
|
||
</view>
|
||
<text class="intro-text">支持按姓名搜索</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">搜索</view>
|
||
<view class="search">
|
||
<input v-model="keyword" type="text" placeholder="输入姓名搜索"/>
|
||
<view class="primary-btn" @tap="searchList">搜索</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">信息列表</view>
|
||
<view v-if="loading" class="empty">加载中...</view>
|
||
<view v-else-if="filteredList.length === 0" class="empty">暂无数据</view>
|
||
<view v-else class="record-list">
|
||
<view class="record-card" v-for="(item, idx) in filteredList" :key="item._rowKey">
|
||
<view class="record-head">
|
||
<view>
|
||
<view class="record-name">{{ item.name || '-' }}</view>
|
||
<view class="record-meta">{{ item.gender || '未知' }} · {{ item.phone || '-' }}</view>
|
||
</view>
|
||
<view class="expand-toggle" @tap.stop="toggleRecordExpand(idx)">
|
||
{{ item._expanded ? '收起' : '展开' }}
|
||
</view>
|
||
</view>
|
||
<view class="record-badges">
|
||
<view class="badge" v-for="tag in getConditionTags(item)" :key="tag">{{ tag }}</view>
|
||
<view v-if="getConditionTags(item).length === 0" class="badge ghost">无三高</view>
|
||
</view>
|
||
<view class="record-row">连锁:{{ item.chainName || '-' }}</view>
|
||
<view v-if="item._expanded" class="record-detail">
|
||
<view class="record-row">出生日期:{{ item.birthday || '-' }}<text class="record-inline-gap">年龄:{{ resolveAge(item.birthday) }}</text></view>
|
||
<view class="record-row">门店:{{ item.merchantName || '-' }}</view>
|
||
<view class="record-row">病史:{{ item.medicalHistory || '-' }}</view>
|
||
<view class="record-row">用药史:{{ item.medicationHistory || '-' }}</view>
|
||
<view class="tracking-block">
|
||
<view class="tracking-head">
|
||
<view class="tracking-title">用药追踪</view>
|
||
<view class="tracking-add-btn" @tap="openTrackingEditor(idx)">新增用药追踪</view>
|
||
</view>
|
||
<view v-if="item._trackingList && item._trackingList.length">
|
||
<scroll-view scroll-x class="tracking-table-scroll">
|
||
<view class="tracking-table">
|
||
<view class="tracking-tr tracking-th">
|
||
<view class="tracking-td name">药品</view>
|
||
<view class="tracking-td small">规格粒/片</view>
|
||
<view class="tracking-td">说明书每天使用/次</view>
|
||
<view class="tracking-td small">每次几粒/片</view>
|
||
<view class="tracking-td">本次购药时间</view>
|
||
<view class="tracking-td small">购买盒数</view>
|
||
<view class="tracking-td">预估服用完时间</view>
|
||
</view>
|
||
<view
|
||
v-for="(tracking, trackingIndex) in item._trackingList"
|
||
:key="tracking._rowKey"
|
||
class="tracking-tr"
|
||
>
|
||
<view class="tracking-td name">{{ tracking.productName || '-' }}</view>
|
||
<view class="tracking-td small">{{ formatField(tracking.dailyUsageCount) }}</view>
|
||
<view class="tracking-td">{{ formatField(tracking.dosagePerTime) }}</view>
|
||
<view class="tracking-td small">{{ formatField(tracking.boxUsageDays) }}</view>
|
||
<view class="tracking-td">{{ tracking.purchaseTime || '-' }}</view>
|
||
<view class="tracking-td small">{{ formatField(tracking.boxCount) }}</view>
|
||
<view class="tracking-td">{{ tracking.estimatedFinishTime || '-' }}</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
<view v-else class="tracking-empty">暂无用药追踪</view>
|
||
</view>
|
||
<view class="record-row">血红蛋白数值:{{ formatField(item.hemoglobinValue) }}</view>
|
||
<view class="record-row">是否贫血:{{ formatEnumField(item.anemiaLevel, '否') }}</view>
|
||
<view class="record-row">是否地贫:{{ formatEnumField(item.thalassemiaType, '否') }}</view>
|
||
<view v-if="hasPressureInfo(item)">
|
||
<view class="record-row">高血压数值:{{ formatField(item.highBloodPressureValue) }}</view>
|
||
<view class="record-row">收缩压:{{ resolveSystolicPressure(item) }}</view>
|
||
<view class="record-row">舒张压:{{ resolveDiastolicPressure(item) }}</view>
|
||
</view>
|
||
<view v-if="hasSugarInfo(item)">
|
||
<view class="record-row">空腹血糖:{{ formatField(item.fastingBloodGlucose) }}</view>
|
||
<view class="record-row">餐后2小时血糖:{{ formatField(item.postprandial2hBloodGlucose) }}</view>
|
||
<view class="record-row">糖化血红蛋白(HbA1c):{{ formatField(item.hba1c) }}</view>
|
||
</view>
|
||
<view v-if="hasLipidInfo(item)">
|
||
<view class="record-row">总胆固醇(TC):{{ formatField(item.totalCholesterol) }}</view>
|
||
<view class="record-row">甘油三酯(TG):{{ formatField(item.triglyceride) }}</view>
|
||
<view class="record-row">低密度脂蛋白(LDL-C):{{ formatField(item.ldlC) }}</view>
|
||
<view class="record-row">高密度脂蛋白(HDL-C):{{ formatField(item.hdlC) }}</view>
|
||
</view>
|
||
<view class="record-row">提交时间:{{ formatTime(item.createTime || item.createDate || item.createdAt) }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<u-popup :show="showTrackingEditor" mode="bottom" round="24rpx" @close="closeTrackingEditor">
|
||
<view class="tracking-editor">
|
||
<view class="tracking-editor-head">
|
||
<view class="picker-action" @tap="closeTrackingEditor">取消</view>
|
||
<text class="picker-title">新增用药追踪</text>
|
||
<view class="picker-action primary" @tap="submitTrackingEditor">保存</view>
|
||
</view>
|
||
<view class="tracking-editor-body">
|
||
<view class="tracking-editor-tip">按当前慢病记录所属连锁拉取药品,保存后会追加到该记录的用药追踪中。</view>
|
||
<view class="form-row">
|
||
<text>药品</text>
|
||
<view class="picker-value tracking-product-value" @tap="openTrackingProductPicker">
|
||
{{ trackingEditorForm.productName || (trackingProductLoading ? '加载中...' : '请选择') }}
|
||
</view>
|
||
</view>
|
||
<view v-if="trackingEditorForm.productName" class="tracking-product-meta">
|
||
<text>规格粒/片:{{ trackingEditorForm.dailyUsageCount || '-' }}</text>
|
||
<text>说明书每天使用/次:{{ trackingEditorForm.dosagePerTime || '-' }}</text>
|
||
<text>每次几粒/片:{{ trackingEditorForm.boxUsageDays || '-' }}</text>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>本次购药时间</text>
|
||
<view class="picker-value" @tap="openTrackingDatePicker">{{ trackingEditorForm.purchaseTime || '请选择' }}</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>购买盒数</text>
|
||
<input
|
||
:value="trackingEditorForm.boxCount"
|
||
type="number"
|
||
placeholder="请输入购买盒数"
|
||
@input="onTrackingBoxInput"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>预估服用完时间</text>
|
||
<view class="readonly-value">{{ trackingEditorForm.estimatedFinishTime || '-' }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
|
||
<u-popup :show="showTrackingProductPicker" mode="bottom" round="24rpx" @close="closeTrackingProductPicker">
|
||
<view class="tracking-product-picker">
|
||
<view class="tracking-editor-head">
|
||
<view class="picker-action" @tap="closeTrackingProductPicker">取消</view>
|
||
<text class="picker-title">选择药品</text>
|
||
<view class="picker-action primary" @tap="closeTrackingProductPicker">完成</view>
|
||
</view>
|
||
<view class="tracking-product-search">
|
||
<input
|
||
v-model="trackingProductKeyword"
|
||
class="tracking-search-input"
|
||
placeholder="搜索药品名称/规格粒片/使用次数"
|
||
placeholder-class="tracking-search-placeholder"
|
||
/>
|
||
</view>
|
||
<view v-if="trackingProductLoading" class="tracking-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<scroll-view v-else class="tracking-product-scroll" scroll-y>
|
||
<view v-if="filteredTrackingProducts.length === 0" class="tracking-empty">暂无药品</view>
|
||
<view
|
||
v-for="(item, idx) in filteredTrackingProducts"
|
||
:key="idx"
|
||
class="tracking-product-option"
|
||
@tap="selectTrackingProduct(idx)"
|
||
>
|
||
<view class="tracking-product-name">{{ item.productName || '-' }}</view>
|
||
<view class="tracking-product-option-meta">规格粒/片:{{ item.dailyUsageCount || '-' }}</view>
|
||
<view class="tracking-product-option-meta">说明书每天使用/次:{{ item.dosagePerTime || '-' }}</view>
|
||
<view class="tracking-product-option-meta">每次几粒/片:{{ item.boxUsageDays || '-' }}</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</u-popup>
|
||
|
||
<u-datetime-picker
|
||
:show="showTrackingDatePicker"
|
||
:value="trackingDatePickerValue"
|
||
:min-date="minDate"
|
||
mode="date"
|
||
@confirm="onTrackingDateConfirm"
|
||
@cancel="closeTrackingDatePicker"
|
||
@close="closeTrackingDatePicker"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import dayjs from "dayjs";
|
||
import {chronicDiseaseCollectionChangeReq, chronicDiseaseCollectionListReq} from "@/api/reimburse";
|
||
import AppNavbar from "@/components/AppNavbar.vue";
|
||
import {getUserInfo} from "@/util/user";
|
||
import {chronicDiseaseProductListReq} from "@/api/product";
|
||
|
||
export default {
|
||
components: {AppNavbar},
|
||
data() {
|
||
return {
|
||
keyword: '',
|
||
list: [],
|
||
loading: false,
|
||
minDate: dayjs().subtract(100, 'year').valueOf(),
|
||
showTrackingEditor: false,
|
||
trackingEditorRecordIndex: -1,
|
||
trackingEditorForm: {
|
||
productId: '',
|
||
goodsCode: '',
|
||
productName: '',
|
||
dailyUsageCount: '',
|
||
dosagePerTime: '',
|
||
boxUsageDays: '',
|
||
purchaseTime: '',
|
||
boxCount: '',
|
||
estimatedFinishTime: ''
|
||
},
|
||
showTrackingProductPicker: false,
|
||
trackingProductLoading: false,
|
||
trackingProductList: [],
|
||
trackingProductKeyword: '',
|
||
trackingProductLoadedChain: '',
|
||
showTrackingDatePicker: false,
|
||
trackingSubmitting: false
|
||
}
|
||
},
|
||
computed: {
|
||
filteredList() {
|
||
const kw = this.keyword.trim()
|
||
if (!kw) return this.list
|
||
return this.list.filter(item => (item.name || '').includes(kw))
|
||
},
|
||
filteredTrackingProducts() {
|
||
const list = Array.isArray(this.trackingProductList) ? this.trackingProductList : []
|
||
const keyword = String(this.trackingProductKeyword || '').trim()
|
||
if (!keyword) return list
|
||
return list.filter(item => {
|
||
const text = [
|
||
item.productName,
|
||
item.goodsCode,
|
||
item.dailyUsageCount,
|
||
item.dosagePerTime,
|
||
item.boxUsageDays
|
||
].join(' ')
|
||
return text.includes(keyword)
|
||
})
|
||
},
|
||
trackingDatePickerValue() {
|
||
return this.trackingEditorForm.purchaseTime ? dayjs(this.trackingEditorForm.purchaseTime).valueOf() : Date.now()
|
||
}
|
||
},
|
||
onShow() {
|
||
this.fetchList()
|
||
},
|
||
methods: {
|
||
async fetchList() {
|
||
this.loading = true
|
||
try {
|
||
const params = this.keyword.trim() ? {name: this.keyword.trim()} : {}
|
||
params.userId = getUserInfo().uid
|
||
const {data} = await chronicDiseaseCollectionListReq(params)
|
||
const list = Array.isArray(data) ? data : (data && (data.list || data.records || data.rows || data.items) ? (data.list || data.records || data.rows || data.items) : [])
|
||
this.list = list.map((item, index) => Object.assign({}, item, {
|
||
_rowKey: this.createRecordKey(item, index),
|
||
_expanded: false,
|
||
_trackingList: this.normalizeTrackingList(item && item.tracking, index)
|
||
}))
|
||
} catch (err) {
|
||
uni.showToast({
|
||
title: '加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
searchList() {
|
||
this.fetchList()
|
||
},
|
||
createTrackingForm() {
|
||
return {
|
||
productId: '',
|
||
goodsCode: '',
|
||
productName: '',
|
||
dailyUsageCount: '',
|
||
dosagePerTime: '',
|
||
boxUsageDays: '',
|
||
purchaseTime: '',
|
||
boxCount: '',
|
||
estimatedFinishTime: ''
|
||
}
|
||
},
|
||
toggleRecordExpand(index) {
|
||
const item = this.filteredList[index]
|
||
if (!item) return
|
||
this.$set(item, '_expanded', !item._expanded)
|
||
},
|
||
openTrackingEditor(index) {
|
||
const item = this.filteredList[index]
|
||
if (!item) return
|
||
if (!String(item.chainName || '').trim()) {
|
||
uni.showToast({
|
||
title: '当前记录缺少连锁',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.trackingEditorRecordIndex = index
|
||
this.trackingEditorForm = this.createTrackingForm()
|
||
this.trackingProductKeyword = ''
|
||
this.showTrackingEditor = true
|
||
},
|
||
closeTrackingEditor() {
|
||
this.showTrackingEditor = false
|
||
this.showTrackingProductPicker = false
|
||
this.showTrackingDatePicker = false
|
||
this.trackingEditorRecordIndex = -1
|
||
this.trackingEditorForm = this.createTrackingForm()
|
||
this.trackingProductKeyword = ''
|
||
},
|
||
getEditingRecord() {
|
||
return this.filteredList[this.trackingEditorRecordIndex] || null
|
||
},
|
||
createRecordKey(item = {}, index = 0) {
|
||
return `record_${item.id || item.phone || item.name || index}_${index}`
|
||
},
|
||
normalizeTrackingList(value, recordIndex = 0) {
|
||
let raw = value
|
||
if (typeof raw === 'string') {
|
||
const text = raw.trim()
|
||
if (!text) return []
|
||
try {
|
||
raw = JSON.parse(text)
|
||
} catch (error) {
|
||
return []
|
||
}
|
||
}
|
||
if (!Array.isArray(raw)) {
|
||
if (raw && typeof raw === 'object') {
|
||
raw = raw.list || raw.records || raw.items || raw.data || []
|
||
} else {
|
||
raw = []
|
||
}
|
||
}
|
||
return raw
|
||
.map((item, index) => Object.assign(this.normalizeTrackingItem(item), {
|
||
_rowKey: `tracking_${recordIndex}_${item.id || item.productId || item.goodsCode || index}_${index}`
|
||
}))
|
||
.filter(item => item.productName || item.purchaseTime || item.boxCount || item.estimatedFinishTime)
|
||
.sort(this.sortTrackingListByFinishTime)
|
||
},
|
||
normalizeTrackingItem(item = {}) {
|
||
return {
|
||
productName: item.productName || item.product_name || item.goodsName || item.goods_name || item.name || '',
|
||
goodsCode: item.goodsCode || item.goods_code || item.code || item.productCode || item.product_code || '',
|
||
productId: item.productId || item.product_id || item.id || item.goodsId || item.goods_id || '',
|
||
dailyUsageCount: item.dailyUsageCount ?? item.daily_usage_count ?? '',
|
||
dosagePerTime: item.dosagePerTime ?? item.dosage_per_time ?? '',
|
||
purchaseTime: this.formatDateOnly(item.purchaseTime || item.purchase_time || item.purchaseDate || item.purchase_date || item.buyTime || item.buy_time),
|
||
boxCount: item.boxCount ?? item.box_count ?? item.purchaseBoxCount ?? item.purchase_box_count ?? item.qty ?? item.quantity ?? '',
|
||
boxUsageDays: item.boxUsageDays ?? item.box_usage_days ?? item.usageDays ?? item.usage_days ?? '',
|
||
estimatedFinishTime: this.formatDateOnly(item.estimatedFinishTime || item.estimated_finish_time || item.finishTime || item.finish_time)
|
||
}
|
||
},
|
||
formatDateOnly(value) {
|
||
if (!value) return ''
|
||
const parsed = dayjs(value)
|
||
return parsed.isValid() ? parsed.format('YYYY-MM-DD') : String(value)
|
||
},
|
||
sortTrackingListByFinishTime(a = {}, b = {}) {
|
||
const timeA = this.resolveTrackingSortTime(a)
|
||
const timeB = this.resolveTrackingSortTime(b)
|
||
if (timeA !== timeB) return timeB - timeA
|
||
const purchaseA = this.resolveTrackingSortTime({estimatedFinishTime: a.purchaseTime})
|
||
const purchaseB = this.resolveTrackingSortTime({estimatedFinishTime: b.purchaseTime})
|
||
return purchaseB - purchaseA
|
||
},
|
||
resolveTrackingSortTime(item = {}) {
|
||
const value = item.estimatedFinishTime || ''
|
||
const parsed = dayjs(value)
|
||
return parsed.isValid() ? parsed.valueOf() : 0
|
||
},
|
||
normalizeTrackingProduct(item) {
|
||
const product = item && item.product && typeof item.product === 'object' ? item.product : {}
|
||
const productId = item.productId || item.product_id || item.id || item.goodsId || item.goods_id || product.productId || product.id || ''
|
||
const goodsCode = item.goodsCode || item.goods_code || item.code || item.productCode || item.product_code || product.goodsCode || product.code || ''
|
||
const productName = item.productName || item.product_name || item.goodsName || item.goods_name || item.name || item.title || product.productName || product.goodsName || product.name || product.title || ''
|
||
const dailyUsageCount = item.dailyUsageCount || item.daily_usage_count || product.dailyUsageCount || product.daily_usage_count || ''
|
||
const dosagePerTime = item.dosagePerTime || item.dosage_per_time || product.dosagePerTime || product.dosage_per_time || ''
|
||
const boxUsageDays = item.boxUsageDays || item.box_usage_days || item.usageDays || item.usage_days || product.boxUsageDays || product.box_usage_days || product.usageDays || product.usage_days || ''
|
||
return {
|
||
productId,
|
||
goodsCode,
|
||
productName,
|
||
dailyUsageCount,
|
||
dosagePerTime,
|
||
boxUsageDays
|
||
}
|
||
},
|
||
uniqueTrackingProducts(list) {
|
||
const map = {}
|
||
;(Array.isArray(list) ? list : []).forEach(item => {
|
||
const key = item.productId || item.goodsCode || `${item.productName}_${item.dailyUsageCount}_${item.dosagePerTime}_${item.boxUsageDays}`
|
||
if (!key || map[key]) return
|
||
map[key] = item
|
||
})
|
||
return Object.keys(map).map(key => map[key])
|
||
},
|
||
async fetchTrackingProducts(chainName = '') {
|
||
const chain = String(chainName || '').trim()
|
||
if (!chain) {
|
||
this.trackingProductList = []
|
||
this.trackingProductLoadedChain = ''
|
||
return
|
||
}
|
||
this.trackingProductLoading = true
|
||
try {
|
||
const {data} = await chronicDiseaseProductListReq({chain})
|
||
if (chain !== String(this.getEditingRecord() && this.getEditingRecord().chainName || '').trim()) return
|
||
const list = this.unwrapList(data)
|
||
.map(item => this.normalizeTrackingProduct(item))
|
||
.filter(item => !!item.productName)
|
||
this.trackingProductList = this.uniqueTrackingProducts(list)
|
||
this.trackingProductLoadedChain = chain
|
||
} catch (error) {
|
||
this.trackingProductList = []
|
||
this.trackingProductLoadedChain = ''
|
||
uni.showToast({
|
||
title: '药品列表获取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.trackingProductLoading = false
|
||
}
|
||
},
|
||
async openTrackingProductPicker() {
|
||
const record = this.getEditingRecord()
|
||
if (!record) return
|
||
this.showTrackingProductPicker = true
|
||
this.trackingProductKeyword = ''
|
||
const chainName = String(record.chainName || '').trim()
|
||
if (this.trackingProductLoadedChain !== chainName || !this.trackingProductList.length) {
|
||
await this.fetchTrackingProducts(chainName)
|
||
}
|
||
},
|
||
closeTrackingProductPicker() {
|
||
this.showTrackingProductPicker = false
|
||
},
|
||
selectTrackingProduct(index) {
|
||
const product = this.filteredTrackingProducts[index]
|
||
if (!product) {
|
||
this.closeTrackingProductPicker()
|
||
return
|
||
}
|
||
const next = Object.assign({}, this.trackingEditorForm, {
|
||
productId: product.productId || '',
|
||
goodsCode: product.goodsCode || '',
|
||
productName: product.productName || '',
|
||
dailyUsageCount: product.dailyUsageCount || '',
|
||
dosagePerTime: product.dosagePerTime || '',
|
||
boxUsageDays: product.boxUsageDays || ''
|
||
})
|
||
next.estimatedFinishTime = this.calculateTrackingFinish(next)
|
||
this.trackingEditorForm = next
|
||
this.closeTrackingProductPicker()
|
||
},
|
||
openTrackingDatePicker() {
|
||
this.showTrackingDatePicker = true
|
||
},
|
||
closeTrackingDatePicker() {
|
||
this.showTrackingDatePicker = false
|
||
},
|
||
onTrackingDateConfirm(e) {
|
||
const next = Object.assign({}, this.trackingEditorForm, {
|
||
purchaseTime: dayjs(e.value).format('YYYY-MM-DD')
|
||
})
|
||
next.estimatedFinishTime = this.calculateTrackingFinish(next)
|
||
this.trackingEditorForm = next
|
||
this.closeTrackingDatePicker()
|
||
},
|
||
onTrackingBoxInput(e) {
|
||
const value = e && e.detail ? e.detail.value : ''
|
||
const next = Object.assign({}, this.trackingEditorForm, {
|
||
boxCount: value
|
||
})
|
||
next.estimatedFinishTime = this.calculateTrackingFinish(next)
|
||
this.trackingEditorForm = next
|
||
},
|
||
parseNumber(value) {
|
||
if (typeof value === 'number') return value
|
||
const match = String(value || '').match(/\d+(\.\d+)?/)
|
||
return match ? Number(match[0]) : NaN
|
||
},
|
||
calculateTrackingFinish(item) {
|
||
const purchaseTime = item && item.purchaseTime ? item.purchaseTime : ''
|
||
const boxUsageDays = this.parseNumber(item && item.boxUsageDays)
|
||
const boxCount = this.parseNumber(item && item.boxCount)
|
||
if (!purchaseTime || !Number.isFinite(boxUsageDays) || boxUsageDays <= 0 || !Number.isFinite(boxCount) || boxCount <= 0) {
|
||
return ''
|
||
}
|
||
return dayjs(purchaseTime).add(boxUsageDays * boxCount, 'day').format('YYYY-MM-DD')
|
||
},
|
||
buildTrackingUpdatePayload(record) {
|
||
const productName = String(this.trackingEditorForm.productName || '').trim()
|
||
const purchaseTime = String(this.trackingEditorForm.purchaseTime || '').trim()
|
||
const boxCount = this.parseNumber(this.trackingEditorForm.boxCount)
|
||
const boxUsageDays = this.parseNumber(this.trackingEditorForm.boxUsageDays)
|
||
if (!productName || !purchaseTime || !Number.isFinite(boxCount) || boxCount <= 0) {
|
||
uni.showToast({
|
||
title: '请完善新增用药追踪信息',
|
||
icon: 'none'
|
||
})
|
||
return null
|
||
}
|
||
if (!Number.isFinite(boxUsageDays) || boxUsageDays <= 0) {
|
||
uni.showToast({
|
||
title: '当前药品缺少每次几粒/片',
|
||
icon: 'none'
|
||
})
|
||
return null
|
||
}
|
||
const nextTracking = Object.assign({}, this.trackingEditorForm, {
|
||
boxCount,
|
||
boxUsageDays,
|
||
estimatedFinishTime: this.calculateTrackingFinish(this.trackingEditorForm)
|
||
})
|
||
if (!nextTracking.estimatedFinishTime) {
|
||
uni.showToast({
|
||
title: '无法计算预估服用完时间',
|
||
icon: 'none'
|
||
})
|
||
return null
|
||
}
|
||
const trackingList = [
|
||
...(Array.isArray(record._trackingList) ? record._trackingList.map(item => this.normalizeTrackingItem(item)) : []),
|
||
this.normalizeTrackingItem(nextTracking)
|
||
].sort(this.sortTrackingListByFinishTime)
|
||
const payload = {}
|
||
Object.keys(record || {}).forEach(key => {
|
||
if (!key.startsWith('_')) payload[key] = record[key]
|
||
})
|
||
payload.tracking = JSON.stringify(trackingList.map(item => ({
|
||
productId: item.productId || '',
|
||
productName: item.productName || '',
|
||
goodsCode: item.goodsCode || '',
|
||
dailyUsageCount: item.dailyUsageCount || '',
|
||
dosagePerTime: item.dosagePerTime || '',
|
||
boxUsageDays: item.boxUsageDays || '',
|
||
purchaseTime: item.purchaseTime || '',
|
||
boxCount: item.boxCount || '',
|
||
estimatedFinishTime: item.estimatedFinishTime || ''
|
||
})))
|
||
return {
|
||
payload,
|
||
trackingList
|
||
}
|
||
},
|
||
async submitTrackingEditor() {
|
||
const record = this.getEditingRecord()
|
||
if (!record || this.trackingSubmitting) return
|
||
const result = this.buildTrackingUpdatePayload(record)
|
||
if (!result) return
|
||
this.trackingSubmitting = true
|
||
try {
|
||
await chronicDiseaseCollectionChangeReq(result.payload)
|
||
record.tracking = result.payload.tracking
|
||
record._trackingList = result.trackingList.map((item, index) => Object.assign({}, item, {
|
||
_rowKey: `tracking_${record.id || this.trackingEditorRecordIndex}_${item.productId || item.goodsCode || index}_${index}`
|
||
}))
|
||
this.trackingSubmitting = false
|
||
uni.showToast({
|
||
title: '用药追踪已新增',
|
||
icon: 'success'
|
||
})
|
||
this.closeTrackingEditor()
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: '保存失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.trackingSubmitting = false
|
||
}
|
||
},
|
||
unwrapList(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result', 'chainList']
|
||
for (let i = 0; i < keys.length; i += 1) {
|
||
const value = data[keys[i]]
|
||
if (Array.isArray(value)) return value
|
||
if (value && typeof value === 'object') {
|
||
const nested = this.unwrapList(value)
|
||
if (nested.length) return nested
|
||
}
|
||
}
|
||
return [data]
|
||
},
|
||
getConditionTags(item) {
|
||
const tags = []
|
||
if (Number(item.highBloodPressure) === 1) tags.push('高血压')
|
||
if (Number(item.highBloodSugar) === 1) tags.push('高血糖')
|
||
if (Number(item.hyperlipidemia) === 1) tags.push('高血脂')
|
||
return tags
|
||
},
|
||
formatField(value) {
|
||
if (value === 0) return '0'
|
||
if (value === null || value === undefined) return '-'
|
||
const text = String(value).trim()
|
||
return text ? text : '-'
|
||
},
|
||
formatEnumField(value, defaultValue = '否') {
|
||
if (value === null || value === undefined) return defaultValue
|
||
const text = String(value).trim()
|
||
return text || defaultValue
|
||
},
|
||
parsePressureValue(value) {
|
||
const text = String(value || '').trim()
|
||
if (!text) return {
|
||
systolic: '',
|
||
diastolic: ''
|
||
}
|
||
const match = text.match(/^(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)$/)
|
||
if (!match) return {
|
||
systolic: '',
|
||
diastolic: ''
|
||
}
|
||
return {
|
||
systolic: match[1] || '',
|
||
diastolic: match[2] || ''
|
||
}
|
||
},
|
||
resolveSystolicPressure(item = {}) {
|
||
if (item.systolicPressure !== undefined && item.systolicPressure !== null && String(item.systolicPressure).trim()) {
|
||
return String(item.systolicPressure).trim()
|
||
}
|
||
const parsed = this.parsePressureValue(item.highBloodPressureValue)
|
||
return this.formatField(parsed.systolic)
|
||
},
|
||
resolveDiastolicPressure(item = {}) {
|
||
if (item.diastolicPressure !== undefined && item.diastolicPressure !== null && String(item.diastolicPressure).trim()) {
|
||
return String(item.diastolicPressure).trim()
|
||
}
|
||
const parsed = this.parsePressureValue(item.highBloodPressureValue)
|
||
return this.formatField(parsed.diastolic)
|
||
},
|
||
hasPressureInfo(item = {}) {
|
||
if (Number(item.highBloodPressure) === 1) return true
|
||
return !!(String(item.highBloodPressureValue || '').trim()
|
||
|| String(item.systolicPressure || '').trim()
|
||
|| String(item.diastolicPressure || '').trim())
|
||
},
|
||
hasSugarInfo(item = {}) {
|
||
if (Number(item.highBloodSugar) === 1) return true
|
||
return !!(String(item.fastingBloodGlucose || '').trim()
|
||
|| String(item.postprandial2hBloodGlucose || '').trim()
|
||
|| String(item.hba1c || '').trim())
|
||
},
|
||
hasLipidInfo(item = {}) {
|
||
if (Number(item.hyperlipidemia) === 1) return true
|
||
return !!(String(item.totalCholesterol || '').trim()
|
||
|| String(item.triglyceride || '').trim()
|
||
|| String(item.ldlC || '').trim()
|
||
|| String(item.hdlC || '').trim())
|
||
},
|
||
formatTime(value) {
|
||
if (!value) return '-'
|
||
return dayjs(value).isValid() ? dayjs(value).format('YYYY-MM-DD HH:mm') : value
|
||
},
|
||
resolveAge(birthday) {
|
||
if (!birthday) return '-'
|
||
const birthDate = dayjs(birthday)
|
||
if (!birthDate.isValid()) return '-'
|
||
const age = dayjs().diff(birthDate, 'year')
|
||
return age >= 0 ? `${age}岁` : '-'
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.chronic-manage-page {
|
||
min-height: 100vh;
|
||
//padding: 0 20rpx 40rpx;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.page-hero {
|
||
//padding: 30rpx 6rpx 18rpx;
|
||
background: url('https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/462bfa04cf0eff8698884c38b9d17519.png') no-repeat
|
||
top center / 100% auto;
|
||
}
|
||
|
||
.page-navbar {
|
||
padding: 0 6rpx;
|
||
}
|
||
|
||
.intro-card {
|
||
margin: 18rpx 20rpx 0;
|
||
padding: 20rpx 18rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.card-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.title-bar {
|
||
width: 6rpx;
|
||
height: 30rpx;
|
||
border-radius: 999rpx;
|
||
background: #2fc6a7;
|
||
}
|
||
|
||
.intro-text {
|
||
margin-top: 12rpx;
|
||
font-size: 26rpx;
|
||
color: #6b7280;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.block {
|
||
margin: 18rpx 20rpx 0;
|
||
}
|
||
|
||
.search {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
align-items: center;
|
||
}
|
||
|
||
.search input {
|
||
flex: 1;
|
||
height: 64rpx;
|
||
border: 1rpx solid #e3eaf4;
|
||
border-radius: 12rpx;
|
||
padding: 0 16rpx;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.record-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.record-card {
|
||
border: 1rpx solid #e7eef8;
|
||
border-radius: 16rpx;
|
||
padding: 14rpx;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.record-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.record-name {
|
||
font-weight: 700;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.record-meta {
|
||
color: var(--text-sub);
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.expand-toggle {
|
||
min-width: 88rpx;
|
||
height: 44rpx;
|
||
padding: 0 18rpx;
|
||
border-radius: 999rpx;
|
||
background: #eef4ff;
|
||
color: #2563eb;
|
||
font-size: 23rpx;
|
||
font-weight: 600;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.record-badges {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.badge {
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 999rpx;
|
||
background: #f4f7fb;
|
||
border: 1rpx solid #e3eaf4;
|
||
font-size: 22rpx;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.badge.ghost {
|
||
color: var(--text-sub);
|
||
}
|
||
|
||
.record-row {
|
||
color: var(--text-sub);
|
||
font-size: 24rpx;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.record-inline-gap {
|
||
margin-left: 20rpx;
|
||
}
|
||
|
||
.record-detail {
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.tracking-block {
|
||
margin-top: 14rpx;
|
||
padding: 12rpx;
|
||
border-radius: 14rpx;
|
||
background: #f8fbff;
|
||
border: 1rpx solid #e3eaf4;
|
||
}
|
||
|
||
.tracking-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.tracking-title {
|
||
color: #1f2937;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tracking-add-btn {
|
||
min-width: 140rpx;
|
||
height: 48rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 999rpx;
|
||
background: #e8f7f3;
|
||
color: #0f9f83;
|
||
font-size: 23rpx;
|
||
font-weight: 600;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.tracking-empty {
|
||
margin-top: 12rpx;
|
||
padding: 24rpx 0;
|
||
text-align: center;
|
||
color: #9ca3af;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.tracking-table-scroll {
|
||
margin-top: 12rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.tracking-table {
|
||
min-width: 1260rpx;
|
||
border-top: 1rpx solid #e3eaf4;
|
||
border-left: 1rpx solid #e3eaf4;
|
||
border-radius: 10rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.tracking-tr {
|
||
display: flex;
|
||
min-height: 70rpx;
|
||
}
|
||
|
||
.tracking-th {
|
||
background: #edf6ff;
|
||
color: #1f2937;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tracking-td {
|
||
width: 200rpx;
|
||
padding: 14rpx 12rpx;
|
||
border-right: 1rpx solid #e3eaf4;
|
||
border-bottom: 1rpx solid #e3eaf4;
|
||
box-sizing: border-box;
|
||
color: #4b5563;
|
||
font-size: 23rpx;
|
||
line-height: 1.4;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.tracking-td.name {
|
||
width: 260rpx;
|
||
}
|
||
|
||
.tracking-td.small {
|
||
width: 140rpx;
|
||
}
|
||
|
||
.tracking-editor,
|
||
.tracking-product-picker {
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
background: #ffffff;
|
||
}
|
||
|
||
.tracking-editor-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 28rpx 12rpx;
|
||
}
|
||
|
||
.picker-action {
|
||
font-size: 26rpx;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.picker-action.primary {
|
||
color: #2563eb;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.picker-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.tracking-editor-body {
|
||
padding: 0 24rpx 24rpx;
|
||
}
|
||
|
||
.tracking-editor-tip {
|
||
margin-bottom: 14rpx;
|
||
color: #6b7280;
|
||
font-size: 24rpx;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 14rpx 0;
|
||
border-bottom: 1rpx solid #edf1f7;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.form-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.form-row > text {
|
||
flex: 1;
|
||
max-width: 46%;
|
||
color: var(--text-main);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.form-row input,
|
||
.form-row .picker-value,
|
||
.readonly-value {
|
||
flex: 1;
|
||
text-align: right;
|
||
}
|
||
|
||
.picker-value,
|
||
.readonly-value {
|
||
color: var(--text-main);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.readonly-value {
|
||
color: #2563eb;
|
||
}
|
||
|
||
.tracking-product-value {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.tracking-product-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
margin: 10rpx 0 6rpx;
|
||
padding: 10rpx 14rpx;
|
||
border-radius: 12rpx;
|
||
background: #f8fbff;
|
||
color: #6b7280;
|
||
font-size: 23rpx;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.tracking-product-search {
|
||
padding: 0 24rpx 16rpx;
|
||
}
|
||
|
||
.tracking-search-input {
|
||
height: 72rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 999rpx;
|
||
background: #f3f6fb;
|
||
font-size: 26rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.tracking-search-placeholder {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.tracking-loading {
|
||
height: 420rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.tracking-product-scroll {
|
||
height: 620rpx;
|
||
padding: 0 24rpx 24rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tracking-product-option {
|
||
padding: 20rpx 18rpx;
|
||
border-radius: 16rpx;
|
||
background: #f8fbff;
|
||
border: 1rpx solid #e3eaf4;
|
||
}
|
||
|
||
.tracking-product-option + .tracking-product-option {
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.tracking-product-name {
|
||
color: #1f2937;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.tracking-product-option-meta {
|
||
margin-top: 8rpx;
|
||
color: #6b7280;
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.empty {
|
||
text-align: center;
|
||
color: var(--text-sub);
|
||
padding: 16rpx 0;
|
||
}
|
||
</style>
|