diff --git a/TUICallKit-Vue3/main.js b/TUICallKit-Vue3/main.js
index 5fd1d831..f1d13fa0 100644
--- a/TUICallKit-Vue3/main.js
+++ b/TUICallKit-Vue3/main.js
@@ -1,5 +1,5 @@
import App from './App'
-var baseUrl ='https://admin.zhenyangtang.com.cn/';
+var baseUrl ='https://css.zhenyangtang.com.cn/';
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
diff --git a/TUICallKit-Vue3/tongji/pages/index.vue b/TUICallKit-Vue3/tongji/pages/index.vue
index a1374ce0..0b254e0d 100644
--- a/TUICallKit-Vue3/tongji/pages/index.vue
+++ b/TUICallKit-Vue3/tongji/pages/index.vue
@@ -222,17 +222,24 @@
{{ todayChecked ? '今日血糖已记录' : '今日还没记血糖' }}
-
-
- {{ card.patient_name || '就诊卡' }}
+
+
+
+ {{ card.patient_name || '就诊卡' }}
+
-
+
@@ -246,45 +253,12 @@
@click="ttsSpeaking ? ttsStop() : speakLatestSummary()"
>
- {{ ttsSpeaking ? '停止朗读' : '朗读最新血糖' }}
+ {{ ttsSpeaking ? '停止朗读' : '朗读血糖' }}
-
-
- 今日血糖
-
-
- {{ elderFasting.label }}
-
- {{ elderFasting.value }}
- mmol/L
-
- 偏高,请遵医嘱
-
-
- {{ elderPostprandial.label }}
-
- {{ elderPostprandial.value }}
- mmol/L
-
- 偏高,请遵医嘱
-
-
-
-
-
-
-
-
- {{ opt.label }}
-
-
+
+
+
@@ -292,11 +266,19 @@
{{ rangeText }} · mmol/L
-
+
空腹
-
+
餐后
@@ -344,7 +326,41 @@
+
+
+ {{ opt.label }}
+
+
+
+ 今日血糖
+
+
+ {{ elderFasting.label }}
+
+ {{ elderFasting.value }}
+ mmol/L
+
+ 偏高,请遵医嘱
+
+
+ {{ elderPostprandial.label }}
+
+ {{ elderPostprandial.value }}
+ mmol/L
+
+ 偏高,请遵医嘱
+
+
+
+
+
@@ -375,7 +391,7 @@
加载今日数据中…
-
+
请如实填写,留空表示该项不上传。当天可多次修改。
@@ -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;
diff --git a/TUICallKit-Vue3/tongji/pages/more.vue b/TUICallKit-Vue3/tongji/pages/more.vue
index 36342880..d6b225de 100644
--- a/TUICallKit-Vue3/tongji/pages/more.vue
+++ b/TUICallKit-Vue3/tongji/pages/more.vue
@@ -82,8 +82,8 @@
@@ -173,7 +173,7 @@
@@ -501,6 +501,104 @@
数据每日同步 · 阈值仅作参考,请遵医嘱
+
+
+
+
+
+
+ {{ inputForm.id ? '修改今日血糖' : '录入今日血糖' }}
+ {{ todayText }} · {{ patientName || '本人' }}
+
+
+ ×
+
+
+
+ 加载今日数据中…
+
+
+
+ 请如实填写,留空表示该项不上传。当天可多次修改。
+
+ 血糖(mmol/L)
+
+
+
+ 空腹血糖
+
+
+
+
+
+
+
+
+ 餐后血糖
+
+
+
+
+
+
+
+
+ 其他血糖
+
+
+
+
+
+ 备注
+
+
+
+
+
+
+ 删除
+
+
+ {{ inputForm.submitting ? '提交中…' : (inputForm.id ? '保存修改' : '提交录入') }}
+
+
+
+
+
@@ -1576,6 +1674,18 @@ const todayText = computed(() => {
return `${d.getFullYear()}-${m}-${day} ${weekdays[d.getDay()]}`
})
+const emptyInputForm = () => ({
+ visible: false,
+ loading: false,
+ submitting: false,
+ id: 0,
+ fasting_blood_sugar: '',
+ postprandial_blood_sugar: '',
+ other_blood_sugar: '',
+ remark: ''
+})
+const inputForm = ref(emptyInputForm())
+
const emptyBpForm = () => ({
visible: false,
loading: false,
@@ -1593,6 +1703,124 @@ function prepareRecordOverlay() {
ttsStop()
}
+function onInputField(field, e) {
+ inputForm.value[field] = e?.detail?.value ?? ''
+}
+
+async function openInputForm() {
+ if (!diagnosisId.value) {
+ showUserToast('请先选择就诊卡')
+ return
+ }
+ const token = uni.getStorageSync('token')
+ if (!token) {
+ showUserToast('请先登录后再录入')
+ return
+ }
+ prepareRecordOverlay()
+ inputForm.value = { ...emptyInputForm(), visible: true, loading: true }
+ try {
+ const res = await proxy.apiUrl({
+ url: '/api/tcm/dailyTodayBloodRecord',
+ method: 'GET',
+ data: { diagnosis_id: diagnosisId.value }
+ }, false)
+ if (res && res.code === 1 && res.data && res.data.record) {
+ const r = res.data.record
+ inputForm.value.id = Number(r.id) || 0
+ inputForm.value.fasting_blood_sugar = numToStr(r.fasting_blood_sugar)
+ inputForm.value.postprandial_blood_sugar = numToStr(r.postprandial_blood_sugar)
+ inputForm.value.other_blood_sugar = numToStr(r.other_blood_sugar)
+ inputForm.value.remark = String(r.remark || '')
+ }
+ } catch (e) {
+ /* 静默:拉取失败时仍允许新增录入 */
+ } finally {
+ inputForm.value.loading = false
+ }
+}
+
+function closeInputForm() {
+ if (inputForm.value.submitting) return
+ inputForm.value.visible = false
+}
+
+async function submitInputForm() {
+ if (inputForm.value.submitting || inputForm.value.loading) return
+ const f = inputForm.value
+ const hasAny = [
+ f.fasting_blood_sugar, f.postprandial_blood_sugar, f.other_blood_sugar
+ ].some((v) => v !== '' && v !== null && Number(v) > 0) || !!(f.remark && f.remark.trim())
+ if (!hasAny) {
+ showUserToast('请至少填写一项数据')
+ return
+ }
+ inputForm.value.submitting = true
+ try {
+ const res = await proxy.apiUrl({
+ url: '/api/tcm/dailySaveBloodRecord',
+ method: 'POST',
+ data: {
+ diagnosis_id: diagnosisId.value,
+ fasting_blood_sugar: f.fasting_blood_sugar,
+ postprandial_blood_sugar: f.postprandial_blood_sugar,
+ other_blood_sugar: f.other_blood_sugar,
+ remark: f.remark
+ }
+ }, false)
+ if (res && res.code === 1) {
+ showUserToast(res.msg || '已保存', { icon: 'success' })
+ inputForm.value.visible = false
+ await fetchAll()
+ await fetchGamifyState()
+ if (claimablePoints.value > 0) {
+ showUserToast('血糖已记录,点击浇水领取积分', { duration: 2800 })
+ }
+ } else {
+ showUserToast((res && res.msg) || '保存失败')
+ }
+ } catch (e) {
+ showUserToast('网络异常,请稍后再试')
+ } finally {
+ inputForm.value.submitting = false
+ }
+}
+
+function deleteInputRecord() {
+ if (!inputForm.value.id || inputForm.value.submitting) return
+ uni.showModal({
+ title: '删除确认',
+ content: '确定删除今天已录入的血糖数据吗?删除后无法恢复。',
+ confirmColor: '#dc2626',
+ success: async (m) => {
+ if (!m.confirm) return
+ inputForm.value.submitting = true
+ try {
+ const res = await proxy.apiUrl({
+ url: '/api/tcm/dailyDeleteBloodRecord',
+ method: 'POST',
+ data: {
+ diagnosis_id: diagnosisId.value,
+ id: inputForm.value.id
+ }
+ }, false)
+ if (res && res.code === 1) {
+ uni.showToast({ title: '已删除', icon: 'success' })
+ inputForm.value.visible = false
+ await fetchAll()
+ await fetchGamifyState()
+ } else {
+ showUserToast((res && res.msg) || '删除失败')
+ }
+ } catch (e) {
+ uni.showToast({ title: '网络异常,请稍后再试', icon: 'none' })
+ } finally {
+ inputForm.value.submitting = false
+ }
+ }
+ })
+}
+
function onBpField(field, e) {
const val = e?.detail?.value ?? ''
bpForm.value[field] = val
@@ -1760,7 +1988,7 @@ const exerciseForm = ref(emptyExerciseForm())
/** 任一录入弹层打开时需卸载 canvas(小程序原生组件会盖住普通 view)*/
const recordOverlayOpen = computed(() =>
- bpForm.value.visible || dietForm.value.visible || exerciseForm.value.visible
+ inputForm.value.visible || bpForm.value.visible || dietForm.value.visible || exerciseForm.value.visible
)
function onExerciseField(field, e) {
@@ -3082,7 +3310,7 @@ function onGamifyTaskTap(task) {
}
if (!task.completed) {
if (task.id === 'blood') {
- goGlucosePage()
+ openInputForm()
return
}
if (task.id === 'diet') {
@@ -4843,6 +5071,7 @@ function onFamilyLikeStripTap() {
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%;
@@ -4854,13 +5083,22 @@ function onFamilyLikeStripTap() {
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;
@@ -4912,19 +5150,27 @@ function onFamilyLikeStripTap() {
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;
diff --git a/TUICallKit-Vue3/tongji/styles/daily-theme.scss b/TUICallKit-Vue3/tongji/styles/daily-theme.scss
index 432d8007..fb18ca2e 100644
--- a/TUICallKit-Vue3/tongji/styles/daily-theme.scss
+++ b/TUICallKit-Vue3/tongji/styles/daily-theme.scss
@@ -44,6 +44,12 @@
+ --warning: #d97706;
+
+ --warning-light: #fff7ed;
+
+
+
--text-primary: #1b1c1a;
--text-secondary: #414941;
@@ -260,7 +266,7 @@
.daily-page .legend-dot-postprandial {
- background: var(--primary-container);
+ background: var(--warning);
}
@@ -556,15 +562,27 @@
+.daily-page .elder-card-scroll {
+
+ margin-top: 24rpx;
+
+ width: 100%;
+
+ white-space: nowrap;
+
+}
+
+
+
.daily-page .elder-card-row {
- display: flex;
+ display: inline-flex;
- flex-wrap: wrap;
+ flex-wrap: nowrap;
gap: 16rpx;
- margin-top: 24rpx;
+ padding: 2rpx 0;
}
@@ -572,6 +590,8 @@
.daily-page .elder-card-chip {
+ flex-shrink: 0;
+
padding: 12rpx 28rpx;
border-radius: 999rpx;
@@ -620,9 +640,11 @@
display: flex;
- flex-direction: column;
+ flex-direction: row;
- gap: 20rpx;
+ align-items: stretch;
+
+ gap: 16rpx;
}
@@ -636,14 +658,18 @@
justify-content: center;
- gap: 16rpx;
+ gap: 12rpx;
min-height: 112rpx;
+ min-width: 0;
+
border-radius: 24rpx;
box-shadow: var(--shadow-sm);
+ box-sizing: border-box;
+
}
@@ -660,6 +686,8 @@
.daily-page .elder-btn-primary {
+ flex: 8;
+
background: var(--primary);
@@ -676,6 +704,14 @@
.daily-page .elder-btn-voice {
+ flex: 2;
+
+ flex-direction: column;
+
+ gap: 8rpx;
+
+ padding: 12rpx 8rpx;
+
background: var(--surface);
border: 2rpx solid var(--border-soft);
@@ -686,6 +722,12 @@
color: var(--primary-dark);
+ font-size: 24rpx;
+
+ line-height: 1.25;
+
+ text-align: center;
+
}
@@ -936,9 +978,7 @@
-.daily-page .elder-chart-block {
- margin: 0 32rpx 24rpx;
-}
+
.daily-page .elder-chart-block .range-bar {
margin-bottom: 16rpx;