更新
This commit is contained in:
@@ -222,17 +222,24 @@
|
||||
<text class="elder-status" :class="todayChecked ? 'is-done' : 'is-pending'">
|
||||
{{ todayChecked ? '今日血糖已记录' : '今日还没记血糖' }}
|
||||
</text>
|
||||
<view v-if="cards.length > 1" class="elder-card-row">
|
||||
<view
|
||||
v-for="card in cards"
|
||||
:key="card.id"
|
||||
class="elder-card-chip"
|
||||
:class="{ active: Number(card.id) === Number(diagnosisId) }"
|
||||
@click="onSelectCard(card)"
|
||||
>
|
||||
<text>{{ card.patient_name || '就诊卡' }}</text>
|
||||
<scroll-view
|
||||
v-if="cards.length > 1"
|
||||
scroll-x
|
||||
class="elder-card-scroll"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="elder-card-row">
|
||||
<view
|
||||
v-for="card in cards"
|
||||
:key="card.id"
|
||||
class="elder-card-chip"
|
||||
:class="{ active: Number(card.id) === Number(diagnosisId) }"
|
||||
@click="onSelectCard(card)"
|
||||
>
|
||||
<text>{{ card.patient_name || '就诊卡' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view class="elder-actions">
|
||||
@@ -246,45 +253,12 @@
|
||||
@click="ttsSpeaking ? ttsStop() : speakLatestSummary()"
|
||||
>
|
||||
<TongjiIcon :name="ttsSpeaking ? 'pause' : 'volume'" size="md" color="#204E2B" />
|
||||
<text class="elder-btn-text">{{ ttsSpeaking ? '停止朗读' : '朗读最新血糖' }}</text>
|
||||
<text class="elder-btn-text">{{ ttsSpeaking ? '停止朗读' : '朗读血糖' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="elder-today-panel">
|
||||
<text class="elder-panel-title">今日血糖</text>
|
||||
<view class="elder-today-grid">
|
||||
<view class="elder-metric" :class="{ 'is-high': elderFasting.isHigh }">
|
||||
<text class="elder-metric-label">{{ elderFasting.label }}</text>
|
||||
<view class="elder-metric-value-row">
|
||||
<text class="elder-metric-value">{{ elderFasting.value }}</text>
|
||||
<text v-if="elderFasting.value !== '—'" class="elder-metric-unit">mmol/L</text>
|
||||
</view>
|
||||
<text v-if="elderFasting.isHigh" class="elder-metric-flag">偏高,请遵医嘱</text>
|
||||
</view>
|
||||
<view class="elder-metric" :class="{ 'is-high': elderPostprandial.isHigh }">
|
||||
<text class="elder-metric-label">{{ elderPostprandial.label }}</text>
|
||||
<view class="elder-metric-value-row">
|
||||
<text class="elder-metric-value">{{ elderPostprandial.value }}</text>
|
||||
<text v-if="elderPostprandial.value !== '—'" class="elder-metric-unit">mmol/L</text>
|
||||
</view>
|
||||
<text v-if="elderPostprandial.isHigh" class="elder-metric-flag">偏高,请遵医嘱</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 血糖趋势(主页面)-->
|
||||
<view v-if="diagnosisId" class="elder-chart-block">
|
||||
<view class="range-bar">
|
||||
<view
|
||||
v-for="opt in rangeOptions"
|
||||
:key="opt.value"
|
||||
class="range-item"
|
||||
:class="{ active: rangeMode === opt.value }"
|
||||
@click="onChangeRange(opt.value)"
|
||||
>
|
||||
<text>{{ opt.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 血糖趋势(主页面)-->
|
||||
<view v-if="diagnosisId" class="elder-chart-block">
|
||||
|
||||
<view class="card chart-card">
|
||||
<view class="card-head">
|
||||
<view class="card-head-left">
|
||||
@@ -292,11 +266,19 @@
|
||||
<text class="card-subtitle">{{ rangeText }} · mmol/L</text>
|
||||
</view>
|
||||
<view class="card-legend">
|
||||
<view class="legend-item">
|
||||
<view
|
||||
class="legend-item"
|
||||
:class="{ inactive: !chartSeriesVisible.fasting }"
|
||||
@click="toggleChartSeries('fasting')"
|
||||
>
|
||||
<view class="legend-dot legend-dot-fasting" />
|
||||
<text>空腹</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view
|
||||
class="legend-item"
|
||||
:class="{ inactive: !chartSeriesVisible.postprandial }"
|
||||
@click="toggleChartSeries('postprandial')"
|
||||
>
|
||||
<view class="legend-dot legend-dot-postprandial" />
|
||||
<text>餐后</text>
|
||||
</view>
|
||||
@@ -344,7 +326,41 @@
|
||||
</cover-view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="range-bar">
|
||||
<view
|
||||
v-for="opt in rangeOptions"
|
||||
:key="opt.value"
|
||||
class="range-item"
|
||||
:class="{ active: rangeMode === opt.value }"
|
||||
@click="onChangeRange(opt.value)"
|
||||
>
|
||||
<text>{{ opt.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="elder-today-panel">
|
||||
<text class="elder-panel-title">今日血糖</text>
|
||||
<view class="elder-today-grid">
|
||||
<view class="elder-metric" :class="{ 'is-high': elderFasting.isHigh }">
|
||||
<text class="elder-metric-label">{{ elderFasting.label }}</text>
|
||||
<view class="elder-metric-value-row">
|
||||
<text class="elder-metric-value">{{ elderFasting.value }}</text>
|
||||
<text v-if="elderFasting.value !== '—'" class="elder-metric-unit">mmol/L</text>
|
||||
</view>
|
||||
<text v-if="elderFasting.isHigh" class="elder-metric-flag">偏高,请遵医嘱</text>
|
||||
</view>
|
||||
<view class="elder-metric" :class="{ 'is-high': elderPostprandial.isHigh }">
|
||||
<text class="elder-metric-label">{{ elderPostprandial.label }}</text>
|
||||
<view class="elder-metric-value-row">
|
||||
<text class="elder-metric-value">{{ elderPostprandial.value }}</text>
|
||||
<text v-if="elderPostprandial.value !== '—'" class="elder-metric-unit">mmol/L</text>
|
||||
</view>
|
||||
<text v-if="elderPostprandial.isHigh" class="elder-metric-flag">偏高,请遵医嘱</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="elder-more-link" @click="goMorePage">
|
||||
<view class="elder-more-body">
|
||||
@@ -375,7 +391,7 @@
|
||||
<view v-if="inputForm.loading" class="input-modal-loading">
|
||||
<text>加载今日数据中…</text>
|
||||
</view>
|
||||
<scroll-view v-else scroll-y class="input-modal-body">
|
||||
<scroll-view v-else scroll-y class="input-modal-body input-modal-body-ready">
|
||||
<view class="input-modal-tip">
|
||||
<text>请如实填写,留空表示该项不上传。当天可多次修改。</text>
|
||||
</view>
|
||||
@@ -565,6 +581,8 @@ const trackingNotes = ref([])
|
||||
const hoverInfo = ref(null)
|
||||
let hoverTimer = null
|
||||
|
||||
const chartSeriesVisible = ref({ fasting: true, postprandial: true })
|
||||
|
||||
// 画布上下文相关
|
||||
let canvasNode = null
|
||||
let canvasCtx = null
|
||||
@@ -887,29 +905,13 @@ function elderMetricFromBlood(blood, field, label) {
|
||||
|
||||
const todayBlood = computed(() => bloodByDate.value[todayStr.value] || null)
|
||||
|
||||
const elderFasting = computed(() => {
|
||||
const today = todayBlood.value
|
||||
if (today && toNumber(today.fasting_blood_sugar) !== null) {
|
||||
return elderMetricFromBlood(today, 'fasting_blood_sugar', '空腹')
|
||||
}
|
||||
const latest = latestStat.value.fasting
|
||||
if (latest.value !== '—') {
|
||||
return { label: '空腹(最近)', value: latest.value, isHigh: latest.isHigh }
|
||||
}
|
||||
return { label: '空腹', value: '—', isHigh: false }
|
||||
})
|
||||
const elderFasting = computed(() =>
|
||||
elderMetricFromBlood(todayBlood.value, 'fasting_blood_sugar', '空腹')
|
||||
)
|
||||
|
||||
const elderPostprandial = computed(() => {
|
||||
const today = todayBlood.value
|
||||
if (today && toNumber(today.postprandial_blood_sugar) !== null) {
|
||||
return elderMetricFromBlood(today, 'postprandial_blood_sugar', '餐后')
|
||||
}
|
||||
const latest = latestStat.value.postprandial
|
||||
if (latest.value !== '—') {
|
||||
return { label: '餐后(最近)', value: latest.value, isHigh: latest.isHigh }
|
||||
}
|
||||
return { label: '餐后', value: '—', isHigh: false }
|
||||
})
|
||||
const elderPostprandial = computed(() =>
|
||||
elderMetricFromBlood(todayBlood.value, 'postprandial_blood_sugar', '餐后')
|
||||
)
|
||||
|
||||
// 给某天某字段计算 day-over-day 趋势(用于日常记录列表的指标小标签)
|
||||
function getDayTrend(date, field, precision = 1) {
|
||||
@@ -1800,8 +1802,8 @@ function buildSeries(field) {
|
||||
})
|
||||
}
|
||||
|
||||
function computeYRange(series1, series2) {
|
||||
const values = [...series1, ...series2].filter((v) => v !== null)
|
||||
function computeYRange(...seriesList) {
|
||||
const values = seriesList.flat().filter((v) => v !== null)
|
||||
if (!values.length) return { yMin: 4, yMax: 12 }
|
||||
let min = Math.min(...values)
|
||||
let max = Math.max(...values)
|
||||
@@ -1835,9 +1837,33 @@ function drawChart() {
|
||||
|
||||
const fastingSeries = buildSeries('fasting_blood_sugar')
|
||||
const postSeries = buildSeries('postprandial_blood_sugar')
|
||||
const { yMin, yMax } = computeYRange(fastingSeries, postSeries)
|
||||
const n = chartDates.value.length
|
||||
const threshold = getBloodSugarThresholds(age.value)
|
||||
const seriesDefs = [
|
||||
{
|
||||
key: 'fasting',
|
||||
data: fastingSeries,
|
||||
color: '#204E2B',
|
||||
fillTop: 'rgba(32,78,43,0.18)',
|
||||
fillBottom: 'rgba(32,78,43,0)',
|
||||
label: '空腹',
|
||||
highThreshold: threshold ? threshold.fasting : null
|
||||
},
|
||||
{
|
||||
key: 'postprandial',
|
||||
data: postSeries,
|
||||
color: '#D97706',
|
||||
fillTop: 'rgba(217,119,6,0.18)',
|
||||
fillBottom: 'rgba(217,119,6,0)',
|
||||
label: '餐后',
|
||||
highThreshold: threshold ? threshold.postprandial : null
|
||||
}
|
||||
]
|
||||
const visibleDefs = seriesDefs.filter((item) => chartSeriesVisible.value[item.key])
|
||||
const rangeSources = visibleDefs.length
|
||||
? visibleDefs.map((item) => item.data)
|
||||
: [fastingSeries, postSeries]
|
||||
const { yMin, yMax } = computeYRange(...rangeSources)
|
||||
const valueToY = (v) => padding.top + ch * (1 - (v - yMin) / (yMax - yMin))
|
||||
|
||||
// 1) 背景:纯净浅色 + 阈值以上一抹极淡红
|
||||
@@ -1915,37 +1941,20 @@ function drawChart() {
|
||||
}
|
||||
|
||||
// 5) 绘制曲线
|
||||
const seriesMeta = [
|
||||
{
|
||||
data: fastingSeries,
|
||||
color: '#204E2B',
|
||||
fillTop: 'rgba(32,78,43,0.18)',
|
||||
fillBottom: 'rgba(32,78,43,0)',
|
||||
label: '空腹',
|
||||
highThreshold: threshold ? threshold.fasting : null
|
||||
},
|
||||
{
|
||||
data: postSeries,
|
||||
color: '#386641',
|
||||
fillTop: 'rgba(56,102,65,0.14)',
|
||||
fillBottom: 'rgba(56,102,65,0)',
|
||||
label: '餐后',
|
||||
highThreshold: threshold ? threshold.postprandial : null
|
||||
}
|
||||
]
|
||||
|
||||
const xAt = (i) => (n === 1 ? padding.left + cw / 2 : padding.left + (cw * i) / (n - 1))
|
||||
const coordCache = []
|
||||
const coordByKey = { fasting: [], postprandial: [] }
|
||||
// 7 天内显示圆点,30 天以纯曲线为主
|
||||
const showDots = n <= 10
|
||||
|
||||
seriesMeta.forEach((meta) => {
|
||||
seriesDefs.forEach((meta) => {
|
||||
const projected = meta.data.map((v, i) => ({
|
||||
x: xAt(i),
|
||||
y: v === null ? null : valueToY(v),
|
||||
value: v
|
||||
}))
|
||||
coordCache.push(projected)
|
||||
coordByKey[meta.key] = projected
|
||||
|
||||
if (!chartSeriesVisible.value[meta.key]) return
|
||||
|
||||
const segments = []
|
||||
let cur = []
|
||||
@@ -2066,11 +2075,18 @@ function drawChart() {
|
||||
cw,
|
||||
ch,
|
||||
n,
|
||||
fasting: coordCache[0] || [],
|
||||
postprandial: coordCache[1] || []
|
||||
fasting: coordByKey.fasting,
|
||||
postprandial: coordByKey.postprandial
|
||||
}
|
||||
}
|
||||
|
||||
function toggleChartSeries(key) {
|
||||
if (key !== 'fasting' && key !== 'postprandial') return
|
||||
chartSeriesVisible.value[key] = !chartSeriesVisible.value[key]
|
||||
hoverInfo.value = null
|
||||
scheduleChartRedraw()
|
||||
}
|
||||
|
||||
function onChartTouch(e) {
|
||||
if (!chartCoords || !canvasWidth) return
|
||||
if (!chartDates.value.length) return
|
||||
@@ -2089,8 +2105,8 @@ function onChartTouch(e) {
|
||||
if (idx < 0) idx = 0
|
||||
if (idx > n - 1) idx = n - 1
|
||||
const date = chartDates.value[idx]
|
||||
const fp = chartCoords.fasting[idx]
|
||||
const pp = chartCoords.postprandial[idx]
|
||||
const fp = chartSeriesVisible.value.fasting ? chartCoords.fasting[idx] : null
|
||||
const pp = chartSeriesVisible.value.postprandial ? chartCoords.postprandial[idx] : null
|
||||
const anchorX = fp?.x ?? pp?.x ?? padding.left + cw * (idx / Math.max(1, n - 1))
|
||||
|
||||
const left = Math.max(8, Math.min(canvasWidth - 130, anchorX - 60))
|
||||
@@ -2970,8 +2986,7 @@ async function onFamilyLike() {
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border-radius: var(--radius-card);
|
||||
margin: 28rpx 24rpx 0;
|
||||
padding: 30rpx 28rpx;
|
||||
padding: 30rpx 15rpx;
|
||||
box-shadow: var(--shadow-premium);
|
||||
animation: card-rise 0.45s ease-out both;
|
||||
}
|
||||
@@ -3039,8 +3054,22 @@ async function onFamilyLike() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
margin: -8rpx -12rpx;
|
||||
font-size: 22rpx;
|
||||
color: var(--slate-600);
|
||||
border-radius: 999rpx;
|
||||
transition: opacity 0.2s ease;
|
||||
&.inactive {
|
||||
opacity: 0.42;
|
||||
.legend-dot-fasting,
|
||||
.legend-dot-postprandial {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.72;
|
||||
}
|
||||
}
|
||||
.legend-dot {
|
||||
width: 16rpx;
|
||||
@@ -4003,6 +4032,7 @@ async function onFamilyLike() {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: input-modal-mask-in 0.38s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
.input-modal {
|
||||
width: 100%;
|
||||
@@ -4014,13 +4044,22 @@ async function onFamilyLike() {
|
||||
max-height: 88vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: input-modal-rise 0.28s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
animation: input-modal-rise 0.46s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
@keyframes input-modal-mask-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes input-modal-rise {
|
||||
from { transform: translateY(60rpx); opacity: 0.5; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
from { transform: translate3d(0, 100%, 0); opacity: 0.96; }
|
||||
to { transform: translate3d(0, 0, 0); opacity: 1; }
|
||||
}
|
||||
@keyframes input-modal-content-in {
|
||||
from { opacity: 0; transform: translate3d(0, 24rpx, 0); }
|
||||
to { opacity: 1; transform: translate3d(0, 0, 0); }
|
||||
}
|
||||
.input-modal-grip {
|
||||
width: 80rpx;
|
||||
@@ -4072,19 +4111,27 @@ async function onFamilyLike() {
|
||||
font-weight: 700;
|
||||
}
|
||||
.input-modal-loading {
|
||||
padding: 80rpx 0;
|
||||
min-height: 520rpx;
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
color: var(--slate-600);
|
||||
font-size: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.input-modal-body {
|
||||
flex: 1;
|
||||
min-height: 520rpx;
|
||||
padding: 20rpx 32rpx 28rpx;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
.input-modal-body-ready {
|
||||
animation: input-modal-content-in 0.34s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
.input-modal-tip {
|
||||
background: var(--primary-light);
|
||||
border: 1rpx dashed #99f6e4;
|
||||
|
||||
Reference in New Issue
Block a user