Files
yxe-mp/packageUser/chronic/manage.vue
T
2026-09-29 15:17:13 +08:00

1075 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>