更新
This commit is contained in:
@@ -48,13 +48,15 @@
|
||||
|
||||
|
||||
<view class="st-main">
|
||||
<!-- 1. 录入今日血糖 -->
|
||||
|
||||
<!-- 快捷建档失败提示(toast 字数有限,完整文案展示在页面) -->
|
||||
<view v-if="dailyContextError && !diagnosisId" class="st-daily-context-error" role="alert">
|
||||
<text class="st-daily-context-error-text">{{ dailyContextError }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 2. 血糖趋势(无就诊卡或录入不足 7 天显示示例曲线) -->
|
||||
<!-- 2. 血糖趋势(无就诊卡或累计录入不足 7 天显示示例曲线) -->
|
||||
<view class="st-chart-card">
|
||||
<view class="st-chart-head">
|
||||
<view>
|
||||
<view style="padding: 0 20rpx;">
|
||||
<text class="st-chart-title">血糖趋势</text>
|
||||
<text class="st-chart-sub">{{ rangeText }} · mmol/L</text>
|
||||
|
||||
@@ -94,9 +96,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="chartUseMockData" class="st-chart-foot">
|
||||
<text class="st-chart-mock-hint">示例数据,连续记录满 7 天后显示您的真实趋势</text>
|
||||
<text class="st-chart-mock-hint">示例数据,累计记录满 7 天后显示您的真实趋势</text>
|
||||
</view>
|
||||
<view class="st-game-entry st-game-entry--in-card" @click="goGamePage">
|
||||
<view class="st-game-entry st-game-entry--in-card" @click="goGamePage" style="margin: 0 20rpx 0 20rpx;">
|
||||
<view class="st-game-entry-icon">
|
||||
<TongjiIcon name="activity" size="sm" color="#006c49" />
|
||||
</view>
|
||||
@@ -107,9 +109,9 @@
|
||||
<TongjiIcon name="chevron-right" size="sm" color="#64748b" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="st-training-module">
|
||||
<dev-training-entry inline />
|
||||
</view>
|
||||
<view class="st-training-section">
|
||||
<dev-training-entry inline tone="weekly" />
|
||||
</view>
|
||||
<!-- 3. AI 饮食建议 -->
|
||||
<view v-if="diagnosisId" class="st-ai-section">
|
||||
<view class="st-ai-deco" aria-hidden="true" />
|
||||
@@ -129,7 +131,7 @@
|
||||
<text>{{ dietAiLoading ? '生成中' : '换一换' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="st-ai-sub">读您近 7 天 · 30 天血糖,定制今日三餐</text>
|
||||
<text class="st-ai-sub">读您近 7 天 · 30 天血糖,定制早餐(含驼奶粉)、喝的与三餐</text>
|
||||
|
||||
<view v-if="dietAiLoading && !hasDietAiMealContent && !dietAiStreaming && !dietAiReplacing" class="st-skeleton">
|
||||
<view v-for="n in 2" :key="'sk-' + n" class="st-skeleton-line" />
|
||||
@@ -145,6 +147,18 @@
|
||||
{{ dietAiRecommend.breakfast || (dietAiLoading ? '…' : '—') }}<text v-if="dietAiLoading && dietAiStreamField === 'breakfast'" class="ai-stream-cursor">▍</text>
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="dietAiRecommend.drinks || (dietAiLoading && dietAiStreamField === 'drinks')"
|
||||
class="st-meal-glass st-meal-glass--drinks"
|
||||
>
|
||||
<view class="st-meal-head">
|
||||
<TongjiIcon name="droplet" size="sm" color="#0ea5e9" />
|
||||
<text class="st-meal-label">喝的</text>
|
||||
</view>
|
||||
<text class="st-meal-text">
|
||||
{{ dietAiRecommend.drinks || '…' }}<text v-if="dietAiLoading && dietAiStreamField === 'drinks'" class="ai-stream-cursor">▍</text>
|
||||
</text>
|
||||
</view>
|
||||
<view class="st-meal-glass">
|
||||
<view class="st-meal-head">
|
||||
<TongjiIcon name="leaf" size="sm" color="#006c49" />
|
||||
@@ -248,6 +262,9 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="phone-gate-body">
|
||||
<view v-if="dailyContextError" class="phone-gate-error" role="alert">
|
||||
<text>{{ dailyContextError }}</text>
|
||||
</view>
|
||||
<text class="phone-gate-tip">微信仅提供手机号,请选择性别以便写入就诊档案。</text>
|
||||
<view class="phone-gate-gender">
|
||||
<text class="phone-gate-gender-label">性别</text>
|
||||
@@ -432,11 +449,42 @@ function showUserToast(title, options = {}) {
|
||||
uni.showToast({
|
||||
title: text,
|
||||
icon: options.icon || 'none',
|
||||
duration: options.duration
|
||||
duration: options.duration || 2800
|
||||
})
|
||||
}
|
||||
|
||||
/** 解析接口错误文案(兼容 msg / message / data.error) */
|
||||
function extractApiError(res, fallback = '') {
|
||||
if (!res) return fallback
|
||||
const raw = res.msg ?? res.message ?? res.data?.error ?? res.data?.msg ?? res.errMsg
|
||||
return formatUserMessage(raw, fallback)
|
||||
}
|
||||
|
||||
const diagnosisId = ref(0)
|
||||
/** 手机号快捷建档失败:页面常驻提示(避免 toast 被 7 字截断) */
|
||||
const dailyContextError = ref('')
|
||||
|
||||
function clearDailyContextError() {
|
||||
dailyContextError.value = ''
|
||||
}
|
||||
|
||||
function reportDailyContextError(res, fallback = '无法开始录入', options = {}) {
|
||||
const text = extractApiError(res, fallback)
|
||||
if (!text) return
|
||||
dailyContextError.value = text
|
||||
if (options.alert) {
|
||||
uni.showModal({
|
||||
title: '无法录入血糖',
|
||||
content: text,
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
})
|
||||
} else {
|
||||
const short = text.length > 14 ? `${text.slice(0, 14)}…` : text
|
||||
showUserToast(short, { duration: 3200 })
|
||||
}
|
||||
}
|
||||
|
||||
const patientId = ref(0)
|
||||
const patientName = ref('')
|
||||
const displayPatientName = computed(() => {
|
||||
@@ -632,6 +680,19 @@ const currentRange = computed(() => {
|
||||
return { start: formatDate(start), end: formatDate(end) }
|
||||
})
|
||||
|
||||
/** 拉取血糖时向前多看几天,用于判断「累计满 7 天」是否达标(图表仍只画 currentRange) */
|
||||
const BLOOD_LOOKBACK_DAYS = 30
|
||||
|
||||
const fetchRange = computed(() => {
|
||||
const displayDays = rangeMode.value === '30' ? 30 : 7
|
||||
const lookback = Math.max(BLOOD_LOOKBACK_DAYS, displayDays)
|
||||
const end = new Date()
|
||||
end.setHours(0, 0, 0, 0)
|
||||
const start = new Date(end)
|
||||
start.setDate(start.getDate() - (lookback - 1))
|
||||
return { start: formatDate(start), end: formatDate(end) }
|
||||
})
|
||||
|
||||
const visibleDates = computed(() => {
|
||||
const { start, end } = currentRange.value
|
||||
if (!start || !end) return []
|
||||
@@ -794,27 +855,37 @@ const recordDays = computed(() => {
|
||||
return result
|
||||
})
|
||||
|
||||
function bloodDayHasSugar(b) {
|
||||
if (!b) return false
|
||||
return toNumber(b.fasting_blood_sugar) !== null
|
||||
|| toNumber(b.postprandial_blood_sugar) !== null
|
||||
|| toNumber(b.other_blood_sugar) !== null
|
||||
}
|
||||
|
||||
const bloodHasData = computed(() => {
|
||||
return chartDates.value.some((date) => {
|
||||
const b = bloodByDate.value[date]
|
||||
return b && (toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null)
|
||||
})
|
||||
return chartDates.value.some((date) => bloodDayHasSugar(bloodByDate.value[date]))
|
||||
})
|
||||
|
||||
/** 当前时间范围内有真实录入的天数 */
|
||||
/** 当前图表时间范围内有真实录入的天数(用于小结统计) */
|
||||
const bloodRecordDayCount = computed(() => {
|
||||
return chartDates.value.filter((date) => {
|
||||
const b = bloodByDate.value[date]
|
||||
return b && (toNumber(b.fasting_blood_sugar) !== null || toNumber(b.postprandial_blood_sugar) !== null)
|
||||
}).length
|
||||
return chartDates.value.filter((date) => bloodDayHasSugar(bloodByDate.value[date])).length
|
||||
})
|
||||
|
||||
/** 拉取窗口内累计有血糖录入的天数(不要求连续) */
|
||||
const bloodCumulativeDayCount = computed(() => {
|
||||
let n = 0
|
||||
for (const date of Object.keys(bloodByDate.value)) {
|
||||
if (bloodDayHasSugar(bloodByDate.value[date])) n++
|
||||
}
|
||||
return n
|
||||
})
|
||||
|
||||
const REAL_BLOOD_DAYS_MIN = 7
|
||||
|
||||
/** 无就诊卡或真实录入不足 7 天时,图表用示例数据 */
|
||||
/** 无就诊卡或累计录入不足 7 天时,图表用示例数据 */
|
||||
const chartUseMockData = computed(() => {
|
||||
if (!diagnosisId.value) return true
|
||||
return bloodRecordDayCount.value < REAL_BLOOD_DAYS_MIN
|
||||
return bloodCumulativeDayCount.value < REAL_BLOOD_DAYS_MIN
|
||||
})
|
||||
|
||||
/** 示例曲线:7 天内有波动的空腹/餐后,便于老人理解趋势图形态 */
|
||||
@@ -1154,8 +1225,14 @@ function syncPhoneGateSexFromUser() {
|
||||
}
|
||||
}
|
||||
|
||||
function openPhoneGate() {
|
||||
async function openPhoneGate() {
|
||||
syncPhoneGateSexFromUser()
|
||||
readUserMobile()
|
||||
if (userMobile.value) {
|
||||
phoneGateVisible.value = false
|
||||
await resolveDailyPhoneContext(false)
|
||||
return
|
||||
}
|
||||
phoneGateVisible.value = true
|
||||
}
|
||||
|
||||
@@ -1172,8 +1249,11 @@ async function refreshUserMobile() {
|
||||
return userMobile.value
|
||||
}
|
||||
|
||||
async function ensureDailyContextByPhone(sex) {
|
||||
if (diagnosisId.value) return true
|
||||
async function ensureDailyContextByPhone(sex, options = {}) {
|
||||
if (diagnosisId.value) {
|
||||
clearDailyContextError()
|
||||
return true
|
||||
}
|
||||
|
||||
const payload = {}
|
||||
const sexVal = sex ?? phoneGateSex.value
|
||||
@@ -1188,6 +1268,7 @@ async function ensureDailyContextByPhone(sex) {
|
||||
}, false)
|
||||
|
||||
if (res && res.code === 1 && res.data && res.data.diagnosis_id) {
|
||||
clearDailyContextError()
|
||||
await applyCard({
|
||||
id: res.data.diagnosis_id,
|
||||
patient_id: res.data.patient_id,
|
||||
@@ -1195,17 +1276,32 @@ async function ensureDailyContextByPhone(sex) {
|
||||
age: res.data.age || 0,
|
||||
gender: res.data.gender || 0
|
||||
})
|
||||
if (res.data.created) {
|
||||
if (res.data.created || res.data.linked_existing) {
|
||||
await fetchCardList()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (res && res.data && res.data.need_mobile) {
|
||||
clearDailyContextError()
|
||||
return 'need_mobile'
|
||||
}
|
||||
|
||||
showUserToast(formatUserMessage(res?.msg, '无法开始录入'))
|
||||
reportDailyContextError(res, '无法开始录入', options)
|
||||
return false
|
||||
}
|
||||
|
||||
/** 已绑定手机号时直接关联档案,不再弹出授权建档 */
|
||||
async function resolveDailyPhoneContext(openFormAfter = false) {
|
||||
const ok = await ensureDailyContextByPhone(phoneGateSex.value, { alert: true })
|
||||
if (ok === true) {
|
||||
if (openFormAfter) await openInputForm(true)
|
||||
return true
|
||||
}
|
||||
if (ok === 'need_mobile') {
|
||||
syncPhoneGateSexFromUser()
|
||||
phoneGateVisible.value = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1218,7 +1314,7 @@ async function ensureCanRecordGlucose() {
|
||||
return false
|
||||
}
|
||||
|
||||
const ok = await ensureDailyContextByPhone()
|
||||
const ok = await ensureDailyContextByPhone(undefined, { alert: true })
|
||||
if (ok === 'need_mobile') {
|
||||
openPhoneGate()
|
||||
return false
|
||||
@@ -1251,6 +1347,13 @@ async function onPhoneGateAuthorize(e) {
|
||||
|
||||
phoneBinding.value = true
|
||||
try {
|
||||
readUserMobile()
|
||||
if (userMobile.value) {
|
||||
phoneGateVisible.value = false
|
||||
await resolveDailyPhoneContext(true)
|
||||
return
|
||||
}
|
||||
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/user/getMobileByMnp',
|
||||
method: 'POST',
|
||||
@@ -1262,12 +1365,7 @@ async function onPhoneGateAuthorize(e) {
|
||||
}
|
||||
await refreshUserMobile()
|
||||
phoneGateVisible.value = false
|
||||
const ok = await ensureDailyContextByPhone(phoneGateSex.value)
|
||||
if (ok === true) {
|
||||
await openInputForm(true)
|
||||
} else if (ok === 'need_mobile') {
|
||||
openPhoneGate()
|
||||
}
|
||||
await resolveDailyPhoneContext(true)
|
||||
} catch (err) {
|
||||
showUserToast('网络异常,请稍后再试')
|
||||
} finally {
|
||||
@@ -1290,7 +1388,7 @@ async function onSelectCard(card) {
|
||||
async function fetchAll() {
|
||||
if (!hasAuthToken() || !diagnosisId.value) return
|
||||
loading.value = true
|
||||
const { start, end } = currentRange.value
|
||||
const { start, end } = fetchRange.value
|
||||
try {
|
||||
const [winRes, noteRes] = await Promise.all([
|
||||
proxy.apiUrl({
|
||||
@@ -2490,15 +2588,15 @@ async function bootstrap(options) {
|
||||
if (!target) target = list[0]
|
||||
await applyCard(target)
|
||||
} else {
|
||||
diagnosisId.value = passedDiagnosisId || 0
|
||||
patientId.value = passedPatientId || 0
|
||||
if (passedPatientName) patientName.value = passedPatientName
|
||||
if (passedAge) age.value = passedAge
|
||||
if (passedGender) gender.value = passedGender
|
||||
if (!patientName.value) readUserContext()
|
||||
// 无可见就诊卡:勿用 URL/缓存里的隐藏诊单 ID
|
||||
diagnosisId.value = 0
|
||||
patientId.value = 0
|
||||
patientName.value = ''
|
||||
age.value = 0
|
||||
gender.value = 0
|
||||
readUserMobile()
|
||||
if (!diagnosisId.value && userMobile.value) {
|
||||
await ensureDailyContextByPhone()
|
||||
if (userMobile.value) {
|
||||
await ensureDailyContextByPhone(undefined, { alert: false })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user