| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494 |
- <template>
- <CustomNav title="血压" leftType="back" />
- <view class="page">
- <view class="header">
- <view class="month-selector">
- <button class="btn" @click="prevPeriod">‹</button>
- <view class="period-controls">
- <picker mode="multiSelector" :value="pickerValue" :range="pickerRange" @change="onPickerChange">
- <view class="month-label">{{ displayPeriod }}</view>
- </picker>
- <view class="view-toggle">
- <button :class="['toggle-btn', { active: viewMode === 'month' }]" @click="setViewMode('month')">月</button>
- <button :class="['toggle-btn', { active: viewMode === 'week' }]" @click="setViewMode('week')">周</button>
- </view>
- </view>
- <button class="btn" @click="nextPeriod">›</button>
- </view>
- </view>
- <!-- 趋势图 - 简化canvas设置 -->
- <view class="chart-wrap">
- <view class="chart-header">本月趋势</view>
- <canvas
- canvas-id="bpChart"
- id="bpChart"
- class="chart-canvas"
- :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
- ></canvas>
- </view>
- <view class="content">
- <view class="summary">共 {{ records.length }} 条记录,本月平均:{{ averageSystolic }}/{{ averageDiastolic }} mmHg</view>
- <view class="list">
- <view v-if="records.length === 0" class="empty">暂无记录</view>
- <view v-for="item in records" :key="item.id" class="list-item" :style="{ backgroundColor: getItemColor(item.s, item.d) }">
- <view class="date">{{ item.date }}</view>
- <view class="value">{{ item.s }}/{{ item.d }} mmHg</view>
- </view>
- </view>
- </view>
- <!-- 删除了添加按钮和相关功能,因为这是公共页面,仅供医生或家属查看患者健康数据 -->
- <!-- Removed add button and related functions because this is a public page for doctors or family members to view patient health data -->
- </view>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted, watch, nextTick, onBeforeUnmount, getCurrentInstance } from 'vue'
- import { onShow, onLoad } from '@dcloudio/uni-app'
- import { listBloodPressureByBoundUser } from '@/api/bloodPressure'
- import { createUChart } from '@/composables/useUChart'
- import CustomNav from '@/components/custom-nav.vue'
- import ScaleRuler from '@/components/scale-ruler.vue'
- import { getWeekStart, getWeekEnd, formatDisplayDate, formatPickerDate, daysInMonth, getTodayStart, isAfterTodayDate, isMonthAfterToday, isWeekAfterToday } from '@/utils/date'
- import { getWindowWidth } from '@/utils/platform'
- type RecordItem = { id: string; date: string; s: number; d: number }
- // 当前展示年月
- const current = ref(new Date())
- // 使用 multiSelector 的索引形式: [yearOffset从2000起, month(0-11)]
- const pickerValue = ref([current.value.getFullYear() - 2000, current.value.getMonth()])
- // 视图模式:'month' 或 'week'
- const viewMode = ref<'month' | 'week'>('month')
- // 年月选择器的选项范围(与 height/weight 保持一致)
- const pickerRange = ref([
- Array.from({ length: 50 }, (_, i) => `${2000 + i}年`),
- Array.from({ length: 12 }, (_, i) => `${i + 1}月`)
- ])
- // 明确的canvas尺寸(将由 getCanvasSize 初始化以匹配设备宽度)
- const canvasWidth = ref(700) // 初始值,会在 mounted 时覆盖
- const canvasHeight = ref(320)
- // 获取Canvas实际尺寸的函数 - 参考微信小程序示例使用固定尺寸
- function getCanvasSize(): Promise<{ width: number; height: number }> {
- return new Promise(async (resolve) => {
- const width = await getWindowWidth().catch(() => 375)
- const height = Math.round((320 / 750) * width)
- resolve({ width, height })
- })
- }
- // 使用 formatPickerDate 从 src/utils/date.ts
- const displayYear = computed(() => current.value.getFullYear())
- const displayMonth = computed(() => current.value.getMonth() + 1)
- // 显示周期(支持月/周)
- const displayPeriod = computed(() => {
- if (viewMode.value === 'month') {
- return `${displayYear.value}年 ${displayMonth.value}月`
- } else {
- const weekStart = getWeekStart(current.value)
- const weekEnd = getWeekEnd(current.value)
- return `${formatDisplayDate(weekStart)} - ${formatDisplayDate(weekEnd)}`
- }
- })
- const records = ref<RecordItem[]>([])
- const patientId = ref<string | null>(null)
- const bindingType = ref<string | null>(null)
- // 页面加载时检查是否传入了患者ID和绑定类型
- onLoad((options) => {
- if (options && options.patientId && options.bindingType) {
- patientId.value = options.patientId
- bindingType.value = options.bindingType
- } else {
- // 如果没有传入patientId或bindingType,则弹窗提示并返回上一页
- uni.showToast({
- title: '未携带必要参数',
- icon: 'none',
- duration: 2000
- })
- setTimeout(() => {
- uni.navigateBack()
- }, 2000)
- }
- })
- async function fetchRecords() {
- let startTime = ''
- let endTime = ''
- if (viewMode.value === 'month') {
- const y = current.value.getFullYear()
- const m = current.value.getMonth()
- startTime = new Date(y, m, 1).toISOString()
- const endDate = new Date(y, m + 1, 0)
- endDate.setHours(23, 59, 59, 999)
- endTime = endDate.toISOString()
- } else {
- const weekStart = getWeekStart(current.value)
- const weekEnd = getWeekEnd(current.value)
- startTime = weekStart.toISOString()
- try {
- const we = new Date(weekEnd)
- we.setHours(23, 59, 59, 999)
- endTime = we.toISOString()
- } catch (e) {
- endTime = weekEnd.toISOString()
- }
- }
- try { if (typeof uni !== 'undefined' && uni.showLoading) uni.showLoading({ title: '加载中...' }) } catch (e) {}
- try {
- // 使用新的 ByBoundUser 接口
- if (patientId.value && bindingType.value) {
- const params = {
- patientUserId: patientId.value,
- bindingType: bindingType.value,
- baseQueryRequest: {
- pageNum: 1,
- pageSize: 100,
- startTime,
- endTime
- }
- }
-
- const res: any = await listBloodPressureByBoundUser(params)
- if (res.statusCode === 401) {
- uni.removeStorageSync('token')
- uni.removeStorageSync('role')
- uni.reLaunch({ url: '/pages/public/login/index' })
- return
- }
- if ((res.data as any) && (res.data as any).code === 200) {
- const apiRecords = (res.data as any).data?.records || []
- records.value = apiRecords.map((item: any) => ({ id: String(item.id), date: formatDisplayDate(new Date(item.measureTime)), s: Number(item.systolicPressure || 0), d: Number(item.diastolicPressure || 0) }))
- try { await bpChart.draw(records, current, viewMode) } catch (e) { console.warn('bpChart draw failed', e) }
- } else {
- console.error('Fetch blood-pressure records failed', res.data)
- }
- }
- } catch (e) {
- console.error('Fetch blood-pressure error', e)
- } finally {
- try { if (typeof uni !== 'undefined' && uni.hideLoading) uni.hideLoading() } catch (e) {}
- }
- }
- // 将 records 聚合为每天一个点(取最新记录)
- function aggregateDaily(recordsArr: RecordItem[], year: number, month: number) {
- const map = new Map<number, RecordItem>()
- for (const r of recordsArr) {
- const parts = r.date.split('-')
- if (parts.length >= 3) {
- const y = parseInt(parts[0], 10)
- const m = parseInt(parts[1], 10) - 1
- const d = parseInt(parts[2], 10)
- if (y === year && m === month) {
- // 覆盖同一天,保留最新的(数组头部为最新)
- map.set(d, r)
- }
- }
- }
- // 返回按日索引的数组
- return map
- }
- const averageSystolic = computed(() => {
- if (records.value.length === 0) return '--'
- const sum = records.value.reduce((s, r) => s + r.s, 0)
- return Math.round(sum / records.value.length)
- })
- const averageDiastolic = computed(() => {
- if (records.value.length === 0) return '--'
- const sum = records.value.reduce((s, r) => s + r.d, 0)
- return Math.round(sum / records.value.length)
- })
- // 根据血压值获取颜色
- function getItemColor(s: number, d: number): string {
- if (s < 120 && d < 80) {
- return '#e8f5e8' // 绿
- } else if (s < 140 && d < 90) {
- return '#fff3cd' // 黄
- } else {
- return '#f8d7da' // 红
- }
- }
- // 使用共享日期工具 (src/utils/date.ts)
- // 使用可复用的 chart composable,支持多序列
- const vm = getCurrentInstance()
- const bpChart = createUChart({
- canvasId: 'bpChart',
- vm,
- getCanvasSize,
- seriesNames: ['收缩压', '舒张压'],
- valueAccessors: [ (r: RecordItem) => r.s, (r: RecordItem) => r.d ],
- colors: ['#ff6a00', '#007aff']
- })
- onMounted(() => {
- // 延迟确保DOM渲染完成并设置canvas尺寸
- setTimeout(async () => {
- await nextTick()
- try {
- const size = await getCanvasSize()
- canvasWidth.value = size.width
- canvasHeight.value = size.height
- } catch (e) {
- console.warn('getCanvasSize failed on mounted', e)
- }
- // 拉取数据并绘制
- await fetchRecords()
- try { await bpChart.draw(records, current, viewMode) } catch (e) { console.warn('bpChart draw failed', e) }
- }, 500)
- })
- // 页面显示时检查登录态
- onShow(() => {
- const token = uni.getStorageSync('token')
- if (!token) {
- uni.reLaunch({ url: '/pages/public/login/index' })
- }
- })
- // 监听并更新图表(轻微去抖)
- watch([() => current.value], async () => {
- setTimeout(async () => {
- await bpChart.update(records, current, viewMode)
- }, 100)
- })
- watch([() => records.value], async () => {
- setTimeout(async () => {
- await bpChart.update(records, current, viewMode)
- }, 100)
- }, { deep: true })
- onBeforeUnmount(() => {
- try { bpChart.destroy() } catch (e) { console.warn('bpChart destroy error', e) }
- })
- // 强制重建图表(用于切换月份时彻底刷新)
- async function rebuildChart() {
- try { await bpChart.rebuild(records, current, viewMode) } catch (e) { console.warn('rebuildChart failed', e) }
- }
- // 使用共享日期工具(在 src/utils/date.ts 中定义)
- // 周/月周期导航与 Picker 处理
- async function prevPeriod() {
- const d = new Date(current.value)
- if (viewMode.value === 'month') {
- d.setMonth(d.getMonth() - 1)
- } else {
- d.setDate(d.getDate() - 7)
- }
- current.value = d
- pickerValue.value = [d.getFullYear() - 2000, d.getMonth()]
- await fetchRecords()
- await rebuildChart()
- }
- async function nextPeriod() {
- const d = new Date(current.value)
- if (viewMode.value === 'month') {
- d.setMonth(d.getMonth() + 1)
- if (isMonthAfterToday(d)) {
- uni.showToast && uni.showToast({ title: '不能查看未来的日期', icon: 'none' })
- return
- }
- } else {
- d.setDate(d.getDate() + 7)
- if (isWeekAfterToday(d)) {
- uni.showToast && uni.showToast({ title: '不能查看未来的日期', icon: 'none' })
- return
- }
- }
- current.value = d
- pickerValue.value = [d.getFullYear() - 2000, d.getMonth()]
- await fetchRecords()
- await rebuildChart()
- }
- async function setViewMode(mode: 'month' | 'week') {
- if (viewMode.value !== mode) {
- viewMode.value = mode
- await fetchRecords()
- await rebuildChart()
- }
- }
- async function onPickerChange(e: any) {
- const val = e?.detail?.value || e
- if (Array.isArray(val) && val.length >= 2) {
- const y = 2000 + val[0]
- const m = val[1]
- let d = new Date(y, m, 1)
- // 不允许选择未来的月份
- if (isMonthAfterToday(d)) {
- const today = getTodayStart()
- uni.showToast && uni.showToast({ title: '不能选择未来的月份,已切换到当前月份', icon: 'none' })
- d = new Date(today.getFullYear(), today.getMonth(), 1)
- pickerValue.value = [today.getFullYear() - 2000, today.getMonth()]
- } else {
- pickerValue.value = [val[0], val[1]]
- }
- current.value = d
- await fetchRecords()
- await rebuildChart()
- }
- }
- // 删除了添加逻辑,因为这是公共页面,仅供医生或家属查看患者健康数据
- // Removed add logic because this is a public page for doctors or family members to view patient health data
- // 删除了删除记录功能,因为这是公共页面,仅供医生或家属查看患者健康数据
- // Removed delete record function because this is a public page for doctors or family members to view patient health data
- </script>
- <style scoped>
- .page {
- min-height: calc(100vh);
- padding-top: calc(var(--status-bar-height) + 44px);
- background: #f5f6f8;
- box-sizing: border-box
- }
- .header {
- padding: 20rpx 40rpx
- }
- .month-selector {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12rpx
- }
- .period-controls {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8rpx;
- }
- .view-toggle {
- display: flex;
- gap: 4rpx;
- }
- .toggle-btn {
- padding: 4rpx 12rpx;
- border: 1rpx solid #ddd;
- background: #f5f5f5;
- color: #666;
- border-radius: 6rpx;
- font-size: 24rpx;
- min-width: 60rpx;
- text-align: center;
- }
- .toggle-btn.active {
- background: #ff6a00;
- color: #fff;
- border-color: #ff6a00;
- }
- .month-label {
- font-size: 34rpx;
- color: #333
- }
- .btn {
- background: transparent;
- border: none;
- font-size: 36rpx;
- color: #666
- }
- .content {
- padding: 20rpx 24rpx 100rpx 24rpx
- }
- .chart-wrap {
- height: 380rpx;
- overflow: hidden; /* 隐藏溢出内容 */
- background: #fff;
- border-radius: 12rpx;
- padding: 24rpx;
- margin: 0 24rpx 20rpx 24rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03)
- }
- .chart-header {
- font-size: 32rpx;
- color: #333;
- margin-bottom: 20rpx;
- font-weight: 600
- }
- /* 关键修复:确保canvas样式正确,参考微信小程序示例 */
- .chart-canvas { margin-left: -10rpx;
- height: 320rpx;
- background-color: #FFFFFF;
- display: block;
- }
- .summary {
- padding: 20rpx;
- color: #666;
- font-size: 28rpx
- }
- .list {
- background: #fff;
- border-radius: 12rpx;
- padding: 10rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03)
- }
- .empty {
- padding: 40rpx;
- text-align: center;
- color: #999
- }
- .list-item {
- display: flex;
- align-items: center;
- padding: 20rpx;
- border-bottom: 1rpx solid #f0f0f0
- }
- .list-item .date {
- color: #666
- }
- .list-item .value {
- color: #333;
- font-weight: 600;
- flex: 1;
- text-align: right
- }
- /* 删除了浮动按钮样式,因为这是公共页面,仅供医生或家属查看患者健康数据 */
- /* Removed floating button styles because this is a public page for doctors or family members to view patient health data */
- /* 删除了模态框样式,因为这是公共页面,仅供医生或家属查看患者健康数据 */
- /* Removed modal styles because this is a public page for doctors or family members to view patient health data */
- </style>
|