Commit 3341d188 authored by 王炽's avatar 王炽

66666

parent 74f73c4a
<template>
<view class="shengzhang-test-result-container">
<view class="result-bg" v-if="!showNoValBg">
<image class="result-bg-img0" :src="`${$baseUrl}shengzhangTestResult/1001/resultBg0.jpg`" mode="aspectFit"></image>
<image class="result-bg-img1" :src="`${$baseUrl}shengzhangTestResult/1001/resultBg1.jpg`" mode="aspectFit"></image>
</view>
<view
class="no-val-bg"
v-else
:style="{
backgroundImage: `url(${$baseUrl}shengzhangTestResult/1001/noValBg.jpg)`,
backgroundRepeat: 'no-repeat',
backgroundSize: '100% auto',
backgroundPosition: 'top center',
width: '100%',
}"
>
<!-- <image class="no-val-bg-img" :src="`${$baseUrl}shengzhangTestResult/1001/noValBg.jpg`" mode="widthFix"></image> -->
</view>
<!-- 返回按钮 -->
<!-- <view class="back-btn" @click="backHandler">
<text class="back-text"></text>
</view> -->
<text class="title">生长测评</text>
<image @tap="backHandler" class="back-btn" :src="`${$baseUrl}shengzhangTool/1001/backBtn.png`"></image>
<view class="content-wrapper">
<!-- 顶部导航标签 -->
<view class="nav-tabs">
<view class="tab-item" :class="{ 'active': activeTab === 'latest' }" @click="switchTab('latest')">
<text class="tab-text">最新</text>
</view>
<view class="tab-item" :class="{ 'active': activeTab === 'history' }" @click="switchTab('history')">
<text class="tab-text">历史</text>
</view>
<!-- <view class="tab-decoration">
<text class="star"></text>
<text class="star"></text>
<text class="star"></text>
</view> -->
</view>
<!-- 最新内容容器 -->
<view class="latest-content" v-if="!showNoValBg" :class="{ 'active': activeTab === 'latest' }">
<!-- 宝宝信息卡片 -->
<view class="baby-info-card">
<view class="card-header">
<image class="name-icon" :src="`${$baseUrl}shengzhangTestResult/1001/nameIcon.png`" mode="aspectFit"></image>
<text class="card-title">{{babyInfo?.babyName}}</text>
</view>
<view class="baby-basic-info">
<text class="gender">{{babyInfo?.gender == 'M' ? '男' : '女'}}</text>
<text class="age">{{babyInfo?.monthAge}}月龄</text>
<text class="test-date">测评于{{dateConvert(babyInfo?.assessmentDate)}}</text>
</view>
<view class="measurement-summary">
<view class="values-row">
<text class="measurement-value">{{assessmentData?.height}}cm</text>
<text class="measurement-value">{{assessmentData?.weight}}kg</text>
<text class="measurement-value" v-if="assessmentData?.headCircumference">{{assessmentData?.headCircumference}}cm</text>
<text class="measurement-value">{{assessmentData?.bmi}}</text>
</view>
<view class="labels-row">
<view class="measurement-item">
<text class="measurement-label">身高</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.heightStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">体重</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.weightStatus]}}</text>
</view>
</view>
<view class="measurement-item" v-if="analysisData?.headStatus">
<text class="measurement-label">头围</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.headStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">BMI</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.bmiStatus]}}</text>
</view>
</view>
</view>
</view>
<view class="growth-evaluation">
<text class="evaluation-text">{{contentText.evaluation}}</text>
</view>
</view>
<!-- 生长情况卡片 -->
<view class="growth-status-card">
<view class="card-header">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengzhangqingkuangIcon.png`" mode="aspectFit"></image>
<text class="card-title">生长情况</text>
</view>
<view class="legend">
<view class="legend-item">
<view class="legend-color too-low"></view>
<text class="legend-text">偏低</text>
</view>
<view class="legend-item">
<view class="legend-color slightly-low"></view>
<text class="legend-text">略低</text>
</view>
<view class="legend-item">
<view class="legend-color normal"></view>
<text class="legend-text">正常</text>
</view>
<view class="legend-item">
<view class="legend-color slightly-high"></view>
<text class="legend-text">略高</text>
</view>
<view class="legend-item">
<view class="legend-color too-high"></view>
<text class="legend-text">偏高</text>
</view>
</view>
<view class="measurement-bars">
<view class="bar-item" >
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.heightStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.height}}cm {{statusMap[analysisData?.heightStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">身高</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData.heightPercentile}}%同龄宝宝</text>
</view>
</view>
<view class="bar-item">
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.weightStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.weight}}kg {{statusMap[analysisData?.weightStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">体重</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData.weightPercentile}}%同龄宝宝</text>
</view>
</view>
<view class="bar-item" v-if="assessmentData?.headCircumference">
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.headStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.headCircumference}}cm {{statusMap[analysisData?.headStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">头围</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData.headPercentile}}%同龄宝宝</text>
</view>
</view>
<view class="bar-item">
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.bmiStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.bmi}} {{statusMap[analysisData?.bmiStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">BMI</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData?.bmiPercentile}}%同龄宝宝</text>
</view>
</view>
</view>
</view>
<!-- 生长曲线卡片 -->
<view class="growth-curve-card">
<view class="card-header">
<text class="card-title">生长曲线</text>
</view>
<view class="curve-tabs">
<view class="curve-tab" :class="{ 'active': activeCurveTab === 'height' }" @click="switchCurveTab('height')">
<image v-if="activeCurveTab === 'height'"class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengaoTab0.png`" mode="aspectFit"></image>
<image v-else class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengaoTab1.png`" mode="aspectFit"></image>
</view>
<view class="curve-tab" :class="{ 'active': activeCurveTab === 'weight' }" @click="switchCurveTab('weight')">
<image v-if="activeCurveTab === 'weight'" class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/tizhongTab0.png`" mode="aspectFit"></image>
<image v-else class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/tizhongTab1.png`" mode="aspectFit"></image>
</view>
<view class="curve-tab" :class="{ 'active': activeCurveTab === 'head' }" @click="switchCurveTab('head')">
<image v-if="activeCurveTab === 'head'" class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/touweiTab0.png`" mode="aspectFit"></image>
<image v-else class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/touweiTab1.png`" mode="aspectFit"></image>
</view>
</view>
<view class="graph-legend">
<view class="legend-item">
<view class="legend-color slightly-low"></view>
<text class="legend-text">略低</text>
</view>
<view class="legend-item">
<view class="legend-color normal"></view>
<text class="legend-text">正常</text>
</view>
<view class="legend-item">
<view class="legend-color slightly-high"></view>
<text class="legend-text">略高</text>
</view>
<view class="legend-item">
<view class="legend-color baby-record"></view>
<text class="legend-text">宝宝记录</text>
</view>
</view>
<view class="graph-title-y">
<text class="graph-title-text">{{getYAxisLabel()}}</text>
</view>
<view class="graph-container">
<canvas class="curve-canvas" canvas-id="growthCurve" :style="{ width: totalWidth + 'px', height: '100%' }" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></canvas>
</view>
<view class="graph-title-x">
<text class="graph-title-text">月龄</text>
</view>
</view>
<view class="curve-tips" @click="showCurveTips">
<image class="tips-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengzhangTips.png`" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 历史内容容器 -->
<view class="history-content" :class="{ 'active': activeTab === 'history' }">
<view class="history-list">
<view class="history-item" v-for="(item, index) in historyList" :key="index">
<view class="history-card">
<view class="card-header">
<image class="name-icon" :src="`${$baseUrl}shengzhangTestResult/1001/nameIcon.png`" mode="aspectFit"></image>
<text class="card-title">{{item?.babyName}}</text>
</view>
<view class="baby-basic-info">
<text class="gender">{{item.gender == 'M' ? '男' : '女'}}</text>
<text class="age">{{item?.age}}月龄</text>
<text class="test-date">测评于{{dateConvert(item?.testDate)}}</text>
</view>
<view class="measurement-summary">
<view class="values-row">
<text class="measurement-value">{{item?.height}}cm</text>
<text class="measurement-value">{{item?.weight}}kg</text>
<text class="measurement-value" v-if="item?.head">{{item?.head}}cm</text>
<text class="measurement-value">{{item?.bmi}}</text>
</view>
<view class="labels-row">
<view class="measurement-item">
<text class="measurement-label">身高</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.heightStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">体重</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.weightStatus]}}</text>
</view>
</view>
<view class="measurement-item" v-if="item?.head">
<text class="measurement-label">头围</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.headStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">BMI</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.bmiStatus]}}</text>
</view>
</view>
</view>
</view>
<view class="growth-evaluation">
<text class="evaluation-text">{{item?.evaluation}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 专家咨询按钮 -->
<view class="expert-consult-btn" @click="consultExpert">
<image class="consult-bg" :src="`${$baseUrl}shengzhangTestResult/1001/zhuanjiazixunBtn.png`" mode="aspectFit"></image>
</view>
<!-- 生长曲线提示弹窗 -->
<ShengzhangQuxianTipsPopup
:visible="showTipsPopup"
@close="closeTipsPopup"
/>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad, onShareAppMessage } from "@dcloudio/uni-app";
import { useShengzhangStore } from '../../stores/shengzhangResult.js';
import { formatDate, jump, JumpType } from '../../utils/index.js';
import { getGrowthHistoryList,getGrowthAssessmentDetail } from '../../api/shengzhangTools';
import ShengzhangQuxianTipsPopup from '../../components/shengzhangQuxianTipsPopup.vue';
import { useUserStore } from "@/stores/user";
import { getHealthField } from "@/api/common";
const isRecords = ref(false);
const shareText = ref('')
onLoad((options) => {
isRecords.value = options.isRecords;
// activeTab.value = isRecords.value ? 'history' : 'latest';
})
const showNoValBg = ref(false);
// 导航标签状态
const activeTab = ref('latest') // 默认显示最新内容
// 生长曲线标签状态
const activeCurveTab = ref('height')
// 弹窗状态
const showTipsPopup = ref(false)
// 滑动相关状态
const scrollLeft = ref(0)
const totalWidth = ref(1080) // 36个月 * 30px
// 滚动偏移量
const scrollOffset = ref(0) // X轴绘制元素的偏移量
// 触摸事件相关
const isDragging = ref(false)
const startX = ref(0)
const lastX = ref(0)
const canvasWidth = ref(400) // 可视区域宽度
const babyInfo = ref()
const analysisData = ref({
bmiPercentile: 0,
bmiStatus: "LOW",
headPercentile: 0,
headStatus: "LOW",
heightPercentile: 0,
heightStatus: "LOW",
weightPercentile: 0,
weightStatus: "LOW"
})
const assessmentData = ref({
})
const contentText = ref({
evaluation: "描述",
shareText: "描述",
summary: "描述"
});
const statusBarPercentileMap = {
NORMAL: 177,
SLIGHT_LOW: 110,
SLIGHT_HIGH: 247,
LOW: 43,
HIGH: 314
}
// 历史数据列表
const historyList = ref([])
const dateConvert = (date) => {
if(!date){
return '';
}
const dateArray = date.split('-');
return dateArray[0] + '年' + dateArray[1] + '月' + dateArray[2] + '日';
}
// 生长曲线数据
const curveData = ref([
])
const curveDataPostHeight = ref([]);
const curveDataPostWeight = ref([]);
const curveDataPostHead = ref([]);
// 生成0-36个月的数据点
/**
* @param {number} startValue - 起始值(0个月时的数值)
* @param {number} endValue - 结束值(36个月时的数值)
* @param {string} type - 数据类型('height'|'weight'|'head')
* @returns {Array} 包含36个月数据点的数组
*/
const generateCurveData = (startValue, endValue, type) => {
const data = []
for (let i = 0; i <= 36; i++) {
const value = startValue + (endValue - startValue) * (i / 36)
const point = { month: i }
point[type] = Math.round(value * 10) / 10
data.push(point)
}
return data
}
//获取宝宝曲线数据范围4条线,用于绘制曲线
const generateCurveData1 = (lineData, type, arrayName, minOrMax) =>{
const data = [];
lineData.forEach(element => {
const point = { month: element.monthAge }
point[type] = minOrMax === 'min' ? element?.[arrayName]?.min : element?.[arrayName]?.max
data.push(point);
});
return data;
}
// slightHighRange
// slightLowRange
// 标准生长曲线数据(多条线,延长到36个月)
const standardCurves = ref({
height: {
slightlyLow: generateCurveData1(curveDataPostHeight.value, 'height', 'slightLowRange', 'min'),
// normal: generateCurveData(50, 110, 'height'),
normal: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHeight.value, 'height', 'slightHighRange', 'max')
},
weight: {
slightlyLow: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightHighRange', 'max')
},
head: {
slightlyLow: generateCurveData1(curveDataPostHead.value, 'head', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHead.value, 'head', 'slightHighRange', 'max')
}
})
const statusMap = {
NORMAL: '正常',
SLIGHT_LOW: '略低',
SLIGHT_HIGH: '略高',
LOW: '偏低',
HIGH: '偏高'
}
// 切换导航标签
/**
* @param {string} tab - 要切换的标签名称('latest'|'history')
*/
const switchTab = (tab) => {
activeTab.value = tab
console.log('切换到标签:', tab)
// if(tab === 'latest' && isRecords.value){
// switchCurveTab('height');
// }
}
// 选择历史记录项
/**
* @param {Object} item - 选中的历史记录项
*/
const selectHistoryItem = (item) => {
console.log('选择历史记录:', item)
// 这里可以添加跳转到详情页或更新当前显示数据的逻辑
uni.showToast({
title: '已选择历史记录',
icon: 'success'
})
}
// 切换生长曲线标签
/**
* @param {string} tab - 要切换的曲线类型('height'|'weight'|'head')
*/
const switchCurveTab = (tab) => {
activeCurveTab.value = tab
console.log('切换到曲线标签:', tab)
// 根据不同的标签更新曲线数据
if (tab === 'height') {
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHeight.userDataPoints,'height');
} else if (tab === 'weight') {
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoWeight.userDataPoints,'weight');
} else if (tab === 'head') {
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHead.userDataPoints,'head');
}
// 重新绘制曲线
setTimeout(() => {
drawGrowthCurve()
}, 100)
}
const curveDataConvert = (curveData,type) => {
const data = [];
curveData.forEach(element => {
const point = { month: element.monthAge }
point[type] = element?.["value"]
data.push(point);
});
console.log('curveDataConvert data=', data);
return data;
}
//获取当前宝宝曲线数据,用于绘制曲线
const generateCurveData2 = (lineData, type, arrayName, minOrMax) =>{
const data = [];
lineData.forEach(element => {
const point = { month: element.monthAge }
point[type] = minOrMax === 'min' ? element?.[arrayName]?.min : element?.[arrayName]?.max
data.push(point);
});
return data;
}
// 获取Y轴标签
/**
* @returns {string} 根据当前曲线类型返回对应的Y轴标签
*/
const getYAxisLabel = () => {
if (activeCurveTab.value === 'height') {
return '身高 (cm)'
} else if (activeCurveTab.value === 'weight') {
return '体重 (kg)'
} else if (activeCurveTab.value === 'head') {
return '头围 (cm)'
}
return '身高 (cm)'
}
// 获取Y轴刻度
/**
* @returns {Array<number>} 根据当前曲线类型返回对应的Y轴刻度数组
*/
const getYTicks = () => {
if (activeCurveTab.value === 'height') {
return [40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170]
} else if (activeCurveTab.value === 'weight') {
return [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
} else if (activeCurveTab.value === 'head') {
return [20, 25, 30, 35, 40, 45, 50, 55, 60]
}
return [40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170]
}
// 显示生长曲线提示
/**
* 显示生长曲线说明弹窗
*/
const showCurveTips = () => {
console.log('显示生长曲线提示')
showTipsPopup.value = true
}
// 关闭生长曲线提示弹窗
/**
* 关闭生长曲线说明弹窗
*/
const closeTipsPopup = () => {
showTipsPopup.value = false
}
// 专家咨询
/**
* 处理专家咨询按钮点击事件
*/
const consultExpert = async () => {
console.log('专家在线咨询')
const res = await getHealthField();
if (!res.success) {
uni.showToast({
title: "获取健康字段失败",
icon: "none",
});
return;
}
const { sign, timestamp, appId, partnerUserId, env } = res.data;
jump({
type: JumpType.MINI,
url: "/pages/partner/redirect",
extra: {
appId: "wx81ecfb5aa3fb512f",
envVersion: env,
extraData: {
sign, // 参考 4.请求参数
timestamp, // 参考 4.请求参数
appId, // 参考 4.请求参数
partnerUserId, // 参考 4.请求参数
targetApp:
"/h5/partner/shining-like-a-start/landing-free-consult?sysType=CRF",
},
},
});
}
// 首页组件逻辑
/**
* 处理返回按钮点击事件,尝试返回上一页或跳转到首页
*/
const backHandler = () => {
console.log('backHandler');
try {
uni.navigateBack({
success: () => {
console.log('返回成功')
},
fail: backFailHandler
})
} catch (error) {
console.log('error=', error)
jump({
type: JumpType.INNER,
url: "/pages/index/index"
})
}
}
const backFailHandler = () => {
console.log('backFailHandler');
}
onShareAppMessage(() => {
return {
title: shareText.value,
path: `/pages/shengzhangTestResult/shengzhangTestResult`,
imageUrl: ''
}
})
const shengzhangStore = useShengzhangStore();
// const headCircumference = ref(0);
onMounted(async () => {
//获取历史记录
const historyListData = await getGrowthHistoryList();
if(!historyListData.success || (!historyListData.data || historyListData.data.length == 0)){
showNoValBg.value = true;
return;
}
historyList.value = [];
if(historyListData.success){
historyListData.data.forEach(item => {
let itemData = {};
itemData.gender = item.babyInfo.gender;
itemData.age = item.babyInfo.monthAge;
itemData.testDate = formatDate(item.assessmentDate);
itemData.height = item.assessmentData.height;
itemData.weight = item.assessmentData.weight;
itemData.head = item.assessmentData.headCircumference;
itemData.bmi = item.assessmentData.bmi;
itemData.evaluation = item.evaluation;
itemData.id = item.id;//测评id
itemData.babyName = item.babyInfo.babyName;
itemData.bmiStatus = item.statusAnalysis.bmiStatus;
itemData.heightStatus = item.statusAnalysis.heightStatus;
itemData.weightStatus = item.statusAnalysis.weightStatus;
itemData.headStatus = item.statusAnalysis.headStatus;
historyList.value.push(itemData);
})
}
let shengzhangInfo = {};
if(isRecords.value){
if(historyListData.success){
if(historyListData.data && historyListData.data.length > 0){
shengzhangInfo = historyListData.data[0];
const detailData = await getGrowthAssessmentDetail(shengzhangInfo.id);
if(detailData.success){
shengzhangInfo = detailData.data;
}
}
}
const userStore = useUserStore();
const babyId = userStore.babyInfo?.content?.id;
const babyDataHeight = {
babyId: babyId,
curveType: 'HEIGHT',
startMonth: 0,
endMonth: 36,
};
await shengzhangStore.getGrowthCurveData(babyDataHeight);
const babyDataWeight = {
babyId: babyId,
curveType: 'WEIGHT',
startMonth: 0,
endMonth: 36,
};
await shengzhangStore.getGrowthCurveData(babyDataWeight);
const babyDataHead = {
babyId: babyId,
curveType: 'HEAD',
startMonth: 0,
endMonth: 36,
};
await shengzhangStore.getGrowthCurveData(babyDataHead);
//默认展示身高
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHeight.userDataPoints,'height');
// 初始化绘制曲线
setTimeout(() => {
drawGrowthCurve()
}, 100)
}else{
shengzhangInfo = {...shengzhangStore.shengzhangInfo};
}
shareText.value = shengzhangInfo?.content?.shareText;
console.log('shareText.value=', shareText.value);
//分析结果处理
const data = {...shengzhangInfo.babyInfo};
data.assessmentDate = formatDate(shengzhangInfo.assessmentDate);
babyInfo.value = data;
analysisData.value = shengzhangInfo.analysis;
assessmentData.value = shengzhangInfo.assessmentData;
// headCircumference.value = assessmentData.headCircumference;
// headCircumference.value = null;
contentText.value = shengzhangInfo.content;
//生长曲线处理
curveDataPostHeight.value = shengzhangStore.getGrowthCurveDataInfoHeight.curveData;
curveDataPostWeight.value = shengzhangStore.getGrowthCurveDataInfoWeight.curveData;
curveDataPostHead.value = shengzhangStore.getGrowthCurveDataInfoHead.curveData;
console.log('curveDataPostHeight.value=', curveDataPostHeight.value);
standardCurves.value = {
height: {
// slightlyLow: generateCurveData(45, 105, 'height'),
// normal: generateCurveData(50, 110, 'height'),
slightlyLow: generateCurveData1(curveDataPostHeight.value, 'height', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHeight.value, 'height', 'slightHighRange', 'max'),
// slightlyHigh: generateCurveData(55, 115, 'height')slightHighRange
},
weight: {
// slightlyLow: generateCurveData(2.5, 15.0, 'weight'),
// normal: generateCurveData(3.0, 16.5, 'weight'),
// normal2: generateCurveData(3.2, 17.0, 'weight'), // 新增的a8e6cf颜色曲线
// slightlyHigh: generateCurveData(3.5, 18.0, 'weight')
slightlyLow: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightHighRange', 'max'),
},
head: {
// slightlyLow: generateCurveData(32, 52, 'head'),
// normal: generateCurveData(34, 54, 'head'),
// normal2: generateCurveData(35, 55, 'head'), // 新增的a8e6cf颜色曲线
// slightlyHigh: generateCurveData(36, 56, 'head')
slightlyLow: generateCurveData1(curveDataPostHead.value, 'head', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHead.value, 'head', 'slightHighRange', 'max'),
}
};
// 初始化绘制曲线
setTimeout(() => {
drawGrowthCurve()
}, 100)
})
// 绘制生长曲线
/**
* 绘制完整的生长曲线图表,包括坐标轴、标准曲线和宝宝记录曲线
*/
const drawGrowthCurve = () => {
const query = uni.createSelectorQuery()
query.select('.curve-canvas').boundingClientRect((rect) => {
if (rect) {
const ctx = uni.createCanvasContext('growthCurve')
const width = rect.width
const height = rect.height
canvasWidth.value = width // 更新可视区域宽度
// 清空画布
ctx.clearRect(0, 0, width, height)
// 设置画布边距
const margin = { top: 20, right: 20, bottom: 25, left:30 } // Y轴宽度
const chartWidth = totalWidth.value - margin.left - margin.right
const chartHeight = height - margin.top - margin.bottom
// 获取当前数据类型
const currentType = activeCurveTab.value
const currentCurves = standardCurves.value[currentType]
// 绘制滚动图层(X轴和图表内容)
drawScrollableLayer(ctx, width, height, margin, chartWidth, chartHeight, currentType)
// 绘制固定图层(Y轴)
drawFixedLayer(ctx, width, height, margin, chartHeight, currentType)
ctx.draw()
}
}).exec()
}
// 绘制坐标轴
/**
* @param {Object} ctx - Canvas上下文对象
* @param {number} width - Canvas总宽度
* @param {number} height - Canvas总高度
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawFixedLayer = (ctx, width, height, margin, chartHeight, type) => {
// console.log('drawFixedLayer', width, height, margin, chartHeight, type);
// 绘制Y轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
ctx.setLineWidth(1)
ctx.moveTo(margin.left, margin.top - 8)
ctx.lineTo(margin.left, height - margin.bottom)
ctx.stroke()
// 绘制Y轴箭头
ctx.beginPath()
ctx.moveTo(margin.left, margin.top - 16)
ctx.lineTo(margin.left - 4, margin.top + 8 - 16)
ctx.lineTo(margin.left + 4, margin.top + 8 - 16)
ctx.closePath()
ctx.fill()
// 绘制Y轴刻度和标签
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
const x = margin.left
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
// 刻度线
// ctx.beginPath()
// ctx.setStrokeStyle('#999')
// ctx.setLineWidth(1)
// ctx.moveTo(x, y)
// ctx.lineTo(x - 5, y)
// ctx.stroke()
// 刻度标签
ctx.setFillStyle('#000')
ctx.setFontSize(12)
ctx.setTextAlign('right')
ctx.fillText(tick.toString(), x - 10, y + 4)
})
}
// 绘制标准曲线
/**
* @param {Object} ctx - Canvas上下文对象
* @param {Object} curves - 标准曲线数据对象 {slightlyLow, normal, slightlyHigh}
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawStandardCurves = (ctx, curves, margin, chartWidth, chartHeight, type) => {
// 绘制略低曲线 - 黄色
drawCurve(ctx, curves.slightlyLow, margin, chartWidth, chartHeight, type, '#ffeaa7', 2)
// 绘制正常曲线 - 浅绿色
drawCurve(ctx, curves.normal, margin, chartWidth, chartHeight, type, '#a8e6cf', 2)
// 绘制新增的正常曲线2 - 浅绿色(不重合)
drawCurve(ctx, curves.normal2, margin, chartWidth, chartHeight, type, '#a8e6cf', 2)
// 绘制略高曲线 - 紫色
drawCurve(ctx, curves.slightlyHigh, margin, chartWidth, chartHeight, type, '#d4a5f5', 2)
}
// 绘制单条曲线
/**
* @param {Object} ctx - Canvas上下文对象
* @param {Array} data - 曲线数据点数组,每个元素包含 {month, height/weight/head}
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
* @param {string} color - 曲线颜色(十六进制颜色值)
* @param {number} lineWidth - 曲线线宽
*/
const drawCurve = (ctx, data, margin, chartWidth, chartHeight, type, color, lineWidth) => {
ctx.beginPath()
ctx.setStrokeStyle(color)
ctx.setLineWidth(lineWidth)
data.forEach((point, index) => {
const x = margin.left + (point.month / 36) * chartWidth
let y = 0
// 根据不同的数据类型计算y坐标
if (type === 'height') {
y = margin.top + (1 - (point.height - 40) / 80) * chartHeight
} else if (type === 'weight') {
y = margin.top + (1 - (point.weight - 2) / 16) * chartHeight
} else if (type === 'head') {
y = margin.top + (1 - (point.head - 30) / 30) * chartHeight
}
if (index === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
})
ctx.stroke()
}
// 绘制宝宝记录曲线
/**
* @param {Object} ctx - Canvas上下文对象
* @param {Array} data - 宝宝记录数据点数组,每个元素包含 {month, height/weight/head}
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawBabyCurve = (ctx, data, margin, chartWidth, chartHeight, type) => {
// 绘制折线
ctx.beginPath()
ctx.setStrokeStyle('#8b4513')
ctx.setLineWidth(1)
data.forEach((point, index) => {
const x = margin.left + (point.month / 36) * chartWidth
let y = 0
// 根据不同的数据类型计算y坐标
if (type === 'height') {
y = margin.top + (1 - (point.height - 40) / 80) * chartHeight
} else if (type === 'weight') {
y = margin.top + (1 - (point.weight - 2) / 16) * chartHeight
} else if (type === 'head') {
y = margin.top + (1 - (point.head - 30) / 30) * chartHeight
}
if (index === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
})
ctx.stroke()
// 绘制数据点
data.forEach((point) => {
const x = margin.left + (point.month / 36) * chartWidth
let y = 0
// 根据不同的数据类型计算y坐标
if (type === 'height') {
y = margin.top + (1 - (point.height - 40) / 80) * chartHeight
} else if (type === 'weight') {
y = margin.top + (1 - (point.weight - 2) / 16) * chartHeight
} else if (type === 'head') {
y = margin.top + (1 - (point.head - 30) / 30) * chartHeight
}
ctx.beginPath()
ctx.setFillStyle('#8b4513')
ctx.arc(x, y, 6, 0, 2 * Math.PI)
ctx.fill()
})
}
// 绘制滚动图层
/**
* @param {Object} ctx - Canvas上下文对象
* @param {number} width - Canvas总宽度
* @param {number} height - Canvas总高度
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawScrollableLayer = (ctx, width, height, margin, chartWidth, chartHeight, type) => {
// console.log('drawScrollableLayer', width, height, margin, chartWidth, chartHeight, type);
// 保存当前上下文状态
ctx.save()
// 设置裁剪区域,只显示可视区域
ctx.beginPath()
ctx.rect(margin.left, 0, width - margin.left - margin.right, height)
ctx.clip()
// 应用滚动偏移
ctx.translate(-scrollOffset.value, 0)
// 绘制X轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
ctx.setLineWidth(2)
ctx.moveTo(margin.left, height - margin.bottom)
ctx.lineTo(totalWidth.value - margin.right, height - margin.bottom)
ctx.stroke()
// 绘制X轴箭头
ctx.beginPath()
ctx.moveTo(totalWidth.value - margin.right, height - margin.bottom)
ctx.lineTo(totalWidth.value - margin.right - 8, height - margin.bottom - 4)
ctx.lineTo(totalWidth.value - margin.right - 8, height - margin.bottom + 4)
ctx.closePath()
ctx.setFillStyle('#000')
ctx.fill()
// 先绘制水平网格线
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
if(index != 0){
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
// 绘制水平网格线
ctx.beginPath()
ctx.setStrokeStyle('#faf2e7')
ctx.setLineWidth(1)
ctx.moveTo(margin.left, y)
ctx.lineTo(totalWidth.value - margin.right, y)
ctx.stroke()
}
})
// 绘制X轴刻度(每个月都显示)
const xTicks = []
for (let i = 0; i <= 36; i++) {
xTicks.push(i)
}
xTicks.forEach((tick) => {
const x = margin.left + (tick / 36) * chartWidth + 4
const y = height - margin.bottom
// 刻度标签
ctx.setFillStyle('#000')
ctx.setFontSize(12)
ctx.setTextAlign('center')
ctx.fillText(tick.toString(), x, y + 20)
})
// 绘制坐标轴标签
// ctx.setFillStyle('#000')
// ctx.setFontSize(14)
// ctx.setTextAlign('center')
// ctx.fillText('月龄', totalWidth.value / 2, height - 10)
// 绘制标准曲线
drawStandardCurves(ctx, standardCurves.value[type], margin, chartWidth, chartHeight, type)
// 绘制宝宝记录曲线
drawBabyCurve(ctx, curveData.value, margin, chartWidth, chartHeight, type)
// 恢复上下文状态
ctx.restore()
}
// 获取X轴刻度
/**
* @returns {Array<number>} 返回0-36个月的X轴刻度数组
*/
// const getXTicks = () => {
// const ticks = []
// for (let i = 0; i <= 36; i++) {
// ticks.push(i)
// }
// return ticks
// }
// 处理滑动事件
/**
* @param {Object} e - 滑动事件对象
* @param {number} e.detail.scrollLeft - 当前滑动位置
*/
const onScroll = (e) => {
scrollLeft.value = e.detail.scrollLeft
}
// 设置滚动偏移量
/**
* @param {number} offset - X轴偏移量
*/
const setScrollOffset = (offset) => {
scrollOffset.value = offset
drawGrowthCurve() // 重新绘制图表
}
// 触摸开始事件
const onTouchStart = (e) => {
isDragging.value = true
startX.value = e.touches[0].clientX
lastX.value = e.touches[0].clientX
}
// 触摸移动事件
const onTouchMove = (e) => {
if (!isDragging.value) return
const currentX = e.touches[0].clientX
const deltaX = lastX.value - currentX
// 计算新的滚动偏移量
const newOffset = scrollOffset.value + deltaX
// const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
// 限制滚动范围,允许一定的弹性效果
// if (newOffset >= -50 && newOffset <= maxOffset + 50) {
// scrollOffset.value = newOffset
// drawGrowthCurve() // 重新绘制图表
// }
const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
if (newOffset <= 0) {
scrollOffset.value = 0;
} else if (newOffset >= 767){//maxOffset) {
scrollOffset.value = 767//maxOffset
}else{
scrollOffset.value = newOffset;
}
drawGrowthCurve();
lastX.value = currentX
}
// 触摸结束事件
const onTouchEnd = () => {
isDragging.value = false
return;
// 确保滚动位置在有效范围内
const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
if (scrollOffset.value < 0) {
scrollOffset.value = 0
drawGrowthCurve()
} else if (scrollOffset.value > maxOffset) {
scrollOffset.value = maxOffset
drawGrowthCurve()
}
}
</script>
<style lang="less" scoped>
.shengzhang-test-result-container {
width: 100%;
height: 100%;
// box-sizing: border-box;
position: absolute;
// overflow-x: hidden;
// overflow-y: auto;
background-color: #fef7f2;
.result-bg{
top: 0rpx;
width: 100%;
height: 2700rpx;
position: absolute;
.result-bg-img0{
position: absolute;
top: 0rpx;
width: 100%;
height: 1300rpx;
}
.result-bg-img1{
position: absolute;
top: 1300rpx;
width: 100%;
height: 1400rpx;
}
}
.no-val-bg{
top: 0rpx;
width: 100%;
height: 100%;
position: absolute;
.no-val-bg-img{
position: relative;
top: 0rpx;
width: 100%;
// height: 100%;
bottom: 0rpx;
}
}
// 内容容器
.content-wrapper {
padding-left: 30rpx;
padding-right: 30rpx;
}
// 最新内容容器
.latest-content {
display: none;
&.active {
display: block;
}
}
// 历史内容容器
.history-content {
padding-left: 30rpx;
padding-right: 30rpx;
display: none;
background-color: #fef7f2;
padding-bottom: 10rpx;
&.active {
display: block;
}
.history-list {
margin-top: 46rpx;
margin-bottom: 200rpx;
}
.history-item {
margin-bottom: 30rpx;
}
.history-card {
background-color: #fff;
border-radius: 24rpx;
padding: 50rpx 35rpx 35rpx 35rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
}
}
.baby-basic-info {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.gender {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.age {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.test-date {
font-size: 24rpx;
color: #000;
}
}
.measurement-summary {
display: flex;
flex-direction: column;
margin-bottom: 30rpx;
.values-row {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
.measurement-value {
font-size: 32rpx;
color: #333;
font-weight: bold;
flex: 1;
text-align: center;
}
}
.labels-row {
display: flex;
justify-content: space-between;
.measurement-item {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
justify-content: center;
.measurement-label {
font-size: 24rpx;
color: #666;
margin-right: 8rpx;
}
.measurement-status {
font-size: 22rpx;
color: #52c41a;
display: flex;
align-items: center;
.status-icon {
width: 16rpx;
height: 16rpx;
margin-right: 6rpx;
}
}
}
}
}
.growth-evaluation {
.evaluation-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
}
}
// 返回按钮
.back-btn {
position: absolute;
top: 119rpx;
left: 30rpx;
width: 29rpx;
height: 29rpx;
}
.title{
position: absolute;
top: 112rpx;
font-size: 34rpx;
font-weight: 500;
width: 100%;
text-align: center;
}
// 顶部导航标签
.nav-tabs {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-top: 202rpx;
width: 260rpx;
margin-left: 5rpx;
.tab-item {
width: 116rpx;
height: 51rpx;
// padding: 20rpx 40rpx;
margin-right: 30rpx;
border-radius: 25rpx;
background-color: #fffbed;
transition: all 0.3s ease;
&.active {
background-color: #b27c1e;
.tab-text {
color: #ffffff;
}
}
.tab-text {
font-size: 28rpx;
color: #b27c1e;
font-weight: 500;
align-items: center;
justify-content: center;
display: flex;
width: 100%;
height: 100%;
}
}
// .tab-decoration {
// position: absolute;
// right: 20rpx;
// top: 50%;
// transform: translateY(-50%);
// .star {
// font-size: 20rpx;
// color: #b27c1e;
// margin-left: 10rpx;
// opacity: 0.6;
// }
// }
}
// 宝宝信息卡片
.baby-info-card {
background-color: #fff;
border-radius: 24rpx;
padding: 50rpx 35rpx 35rpx 35rpx;
margin-top: 46rpx;
// height: 100%;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
}
}
.baby-basic-info {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.gender {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.age {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.test-date {
font-size: 24rpx;
color: #000;
}
}
.measurement-summary {
display: flex;
flex-direction: column;
margin-bottom: 30rpx;
.values-row {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
.measurement-value {
font-size: 32rpx;
color: #333;
font-weight: bold;
flex: 1;
text-align: center;
}
}
.labels-row {
display: flex;
justify-content: space-between;
.measurement-item {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
justify-content: center;
.measurement-label {
font-size: 24rpx;
color: #666;
margin-right: 8rpx;
}
.measurement-status {
font-size: 22rpx;
color: #52c41a;
display: flex;
align-items: center;
.status-icon {
width: 16rpx;
height: 16rpx;
margin-right: 6rpx;
}
}
}
}
}
.growth-evaluation {
.evaluation-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
}
// 生长情况卡片
.growth-status-card {
background-color: #fff;
border-radius: 24rpx;
padding: 50rpx 35rpx 15rpx 35rpx;
margin-top: 40rpx;
// box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #000;
font-weight: bold;
}
}
.legend {
display: flex;
justify-content: space-between;
margin-top: 46rpx;
.legend-item {
display: flex;
align-items: center;
.legend-color {
width: 20rpx;
height: 20rpx;
border-radius: 4rpx;
margin-right: 8rpx;
&.too-low {
background-color: #ffede0;
}
&.slightly-low {
background-color: #fde0a5;
}
&.normal {
background-color: #89caa2;
}
&.slightly-high {
background-color: #f3d1e9;
}
&.too-high {
background-color: #a78dbc;
}
}
.legend-text {
font-size: 22rpx;
color: #000;
}
}
}
.measurement-bars {
margin-top: 26rpx;
.bar-item {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
.value-triangle-container {
display: flex;
flex-direction: column;
width: 150rpx;
align-items: center;
.bar-value {
font-size: 24rpx;
color: #b27c1e;
}
.triangle {
// position: relative;
top:8rpx;
// margin-left: 20rpx;
width: 20rpx;
height: 20rpx;
// align-self: center;
}
}
.bar-row {
display: flex;
align-items: center;
justify-content: space-between;
.measurement-label {
font-size: 28rpx;
color: #000;
width: 80rpx;
// margin-top: 3rpx;
}
.value-bar {
flex: 1;
height: 30rpx;
}
.bar-percentage {
font-size: 24rpx;
color: #000;
width: 200rpx;
text-align: right;
}
}
}
}
}
// 生长曲线卡片
.growth-curve-card {
background-color: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-top: 40rpx;
// box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
}
}
.curve-tabs {
display: flex;
justify-content: space-around;
// justify-items: center;
width: 466rpx;
height: 141rpx;
margin-bottom: 27rpx;
margin-top: 27rpx;
margin-left: 78rpx;
background-color: #f6f8fa;
border-radius: 70rpx;
.curve-tab {
margin-top: 8rpx;
display: flex;
align-items: center;
justify-content: center;
width: 132rpx;
height: 131rpx;
border-radius: 50%;
// transition: all 0.3s ease;
// background-color: #ffffff;
// box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
position: relative;
&.active {
// background-color: #e0e0e0;
// box-shadow: none;
}
.tab-icon {
width: 100%;
height: 100%;
}
}
}
.graph-legend {
display: flex;
justify-content: space-around;
.legend-item {
display: flex;
align-items: center;
.legend-color {
width: 20rpx;
height: 20rpx;
border-radius: 4rpx;
margin-right: 6rpx;
&.slightly-low {
background-color: #fde0a5; // 黄色
}
&.normal {
background-color: #89caa2; // 浅绿色
}
&.normal2 {
background-color: #a8e6cf; // 浅绿色(第二条线)
}
&.slightly-high {
background-color: #f3d1e9; // 紫色
}
&.baby-record {
background-color: #b27c1e; // 棕色
}
}
.legend-text {
font-size: 20rpx;
color: #000;
}
}
}
.graph-title-y{
position: relative;
margin-top: 0rpx;
margin-left: 20rpx;
.graph-title-text{
font-size: 20rpx;
color: #000;
}
}
.graph-container {
position: relative;
height: 400rpx;
background-color: #fff;
border-radius: 10rpx;
overflow: hidden;
.curve-canvas {
width: 100%;
height: 100%;
display: block;
}
}
.graph-title-x{
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
.graph-title-text{
font-size: 20rpx;
color: #000;
}
}
}
.curve-tips {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-top: 31rpx;
margin-bottom: 60rpx;
width: 100%;
height: 24rpx;
.tips-icon{
width: 198rpx;
height: 100%;
}
}
// 专家咨询按钮
.expert-consult-btn {
position: fixed;
bottom: 60rpx;
left: 0;
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
.consult-bg {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
}
}
</style>
\ No newline at end of file
<template>
<view class="shengzhang-test-result-container">
<view class="result-bg" v-if="!showNoValBg">
<image class="result-bg-img0" :src="`${$baseUrl}shengzhangTestResult/1001/resultBg0.jpg`" mode="aspectFit"></image>
<image class="result-bg-img1" :src="`${$baseUrl}shengzhangTestResult/1001/resultBg1.jpg`" mode="aspectFit"></image>
</view>
<view
class="no-val-bg"
v-else
:style="{
backgroundImage: `url(${$baseUrl}shengzhangTestResult/1001/noValBg.jpg)`,
backgroundRepeat: 'no-repeat',
backgroundSize: '100% auto',
backgroundPosition: 'top center',
width: '100%',
}"
>
<!-- <image class="no-val-bg-img" :src="`${$baseUrl}shengzhangTestResult/1001/noValBg.jpg`" mode="widthFix"></image> -->
</view>
<!-- 返回按钮 -->
<!-- <view class="back-btn" @click="backHandler">
<text class="back-text"></text>
</view> -->
<text class="title">生长测评</text>
<image @tap="backHandler" class="back-btn" :src="`${$baseUrl}shengzhangTool/1001/backBtn.png`"></image>
<view class="content-wrapper">
<!-- 顶部导航标签 -->
<view class="nav-tabs">
<view class="tab-item" :class="{ 'active': activeTab === 'latest' }" @click="switchTab('latest')">
<text class="tab-text">最新</text>
</view>
<view class="tab-item" :class="{ 'active': activeTab === 'history' }" @click="switchTab('history')">
<text class="tab-text">历史</text>
</view>
<!-- <view class="tab-decoration">
<text class="star"></text>
<text class="star"></text>
<text class="star"></text>
</view> -->
</view>
<!-- 最新内容容器 -->
<view class="latest-content" v-if="!showNoValBg" :class="{ 'active': activeTab === 'latest' }">
<!-- 宝宝信息卡片 -->
<view class="baby-info-card">
<view class="card-header">
<image class="name-icon" :src="`${$baseUrl}shengzhangTestResult/1001/nameIcon.png`" mode="aspectFit"></image>
<text class="card-title">{{babyInfo?.babyName}}</text>
</view>
<view class="baby-basic-info">
<text class="gender">{{babyInfo?.gender == 'M' ? '男' : '女'}}</text>
<text class="age">{{babyInfo?.monthAge}}月龄</text>
<text class="test-date">测评于{{dateConvert(babyInfo?.assessmentDate)}}</text>
</view>
<view class="measurement-summary">
<view class="values-row">
<text class="measurement-value">{{assessmentData?.height}}cm</text>
<text class="measurement-value">{{assessmentData?.weight}}kg</text>
<text class="measurement-value" v-if="assessmentData?.headCircumference">{{assessmentData?.headCircumference}}cm</text>
<text class="measurement-value">{{assessmentData?.bmi}}</text>
</view>
<view class="labels-row">
<view class="measurement-item">
<text class="measurement-label">身高</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.heightStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">体重</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.weightStatus]}}</text>
</view>
</view>
<view class="measurement-item" v-if="analysisData?.headStatus">
<text class="measurement-label">头围</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.headStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">BMI</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[analysisData?.bmiStatus]}}</text>
</view>
</view>
</view>
</view>
<view class="growth-evaluation">
<text class="evaluation-text">{{contentText.evaluation}}</text>
</view>
</view>
<!-- 生长情况卡片 -->
<view class="growth-status-card">
<view class="card-header">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengzhangqingkuangIcon.png`" mode="aspectFit"></image>
<text class="card-title">生长情况</text>
</view>
<view class="legend">
<view class="legend-item">
<view class="legend-color too-low"></view>
<text class="legend-text">偏低</text>
</view>
<view class="legend-item">
<view class="legend-color slightly-low"></view>
<text class="legend-text">略低</text>
</view>
<view class="legend-item">
<view class="legend-color normal"></view>
<text class="legend-text">正常</text>
</view>
<view class="legend-item">
<view class="legend-color slightly-high"></view>
<text class="legend-text">略高</text>
</view>
<view class="legend-item">
<view class="legend-color too-high"></view>
<text class="legend-text">偏高</text>
</view>
</view>
<view class="measurement-bars">
<view class="bar-item" >
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.heightStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.height}}cm {{statusMap[analysisData?.heightStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">身高</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData.heightPercentile}}%同龄宝宝</text>
</view>
</view>
<view class="bar-item">
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.weightStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.weight}}kg {{statusMap[analysisData?.weightStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">体重</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData.weightPercentile}}%同龄宝宝</text>
</view>
</view>
<view class="bar-item" v-if="assessmentData?.headCircumference">
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.headStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.headCircumference}}cm {{statusMap[analysisData?.headStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">头围</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData.headPercentile}}%同龄宝宝</text>
</view>
</view>
<view class="bar-item">
<view class="value-triangle-container" :style="{marginLeft: statusBarPercentileMap[analysisData.bmiStatus] + 'rpx'}">
<text class="bar-value">{{assessmentData?.bmi}} {{statusMap[analysisData?.bmiStatus]}}</text>
<image class="triangle" :src="`${$baseUrl}shengzhangTestResult/1001/triangle.png`" mode="aspectFit"></image>
</view>
<view class="bar-row">
<text class="measurement-label">BMI</text>
<image class="value-bar" :src="`${$baseUrl}shengzhangTestResult/1001/valueBar.png`" mode="aspectFit"></image>
<text class="bar-percentage">超过{{analysisData?.bmiPercentile}}%同龄宝宝</text>
</view>
</view>
</view>
</view>
<!-- 生长曲线卡片 -->
<view class="growth-curve-card">
<view class="card-header">
<text class="card-title">生长曲线</text>
</view>
<view class="curve-tabs">
<view class="curve-tab" :class="{ 'active': activeCurveTab === 'height' }" @click="switchCurveTab('height')">
<image v-if="activeCurveTab === 'height'"class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengaoTab0.png`" mode="aspectFit"></image>
<image v-else class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengaoTab1.png`" mode="aspectFit"></image>
</view>
<view class="curve-tab" :class="{ 'active': activeCurveTab === 'weight' }" @click="switchCurveTab('weight')">
<image v-if="activeCurveTab === 'weight'" class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/tizhongTab0.png`" mode="aspectFit"></image>
<image v-else class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/tizhongTab1.png`" mode="aspectFit"></image>
</view>
<view class="curve-tab" :class="{ 'active': activeCurveTab === 'head' }" @click="switchCurveTab('head')">
<image v-if="activeCurveTab === 'head'" class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/touweiTab0.png`" mode="aspectFit"></image>
<image v-else class="tab-icon" :src="`${$baseUrl}shengzhangTestResult/1001/touweiTab1.png`" mode="aspectFit"></image>
</view>
</view>
<view class="graph-legend">
<view class="legend-item">
<view class="legend-color slightly-low"></view>
<text class="legend-text">略低</text>
</view>
<view class="legend-item">
<view class="legend-color normal"></view>
<text class="legend-text">正常</text>
</view>
<view class="legend-item">
<view class="legend-color slightly-high"></view>
<text class="legend-text">略高</text>
</view>
<view class="legend-item">
<view class="legend-color baby-record"></view>
<text class="legend-text">宝宝记录</text>
</view>
</view>
<view class="graph-title-y">
<text class="graph-title-text">{{getYAxisLabel()}}</text>
</view>
<view class="graph-container">
<canvas class="curve-canvas" canvas-id="growthCurve" :style="{ width: totalWidth + 'px', height: '100%' }" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></canvas>
</view>
<view class="graph-title-x">
<text class="graph-title-text">月龄</text>
</view>
</view>
<view class="curve-tips" @click="showCurveTips">
<image class="tips-icon" :src="`${$baseUrl}shengzhangTestResult/1001/shengzhangTips.png`" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 历史内容容器 -->
<view class="history-content" :class="{ 'active': activeTab === 'history' }">
<view class="history-list">
<view class="history-item" v-for="(item, index) in historyList" :key="index">
<view class="history-card">
<view class="card-header">
<image class="name-icon" :src="`${$baseUrl}shengzhangTestResult/1001/nameIcon.png`" mode="aspectFit"></image>
<text class="card-title">{{item?.babyName}}</text>
</view>
<view class="baby-basic-info">
<text class="gender">{{item.gender == 'M' ? '男' : '女'}}</text>
<text class="age">{{item?.age}}月龄</text>
<text class="test-date">测评于{{dateConvert(item?.testDate)}}</text>
</view>
<view class="measurement-summary">
<view class="values-row">
<text class="measurement-value">{{item?.height}}cm</text>
<text class="measurement-value">{{item?.weight}}kg</text>
<text class="measurement-value" v-if="item?.head">{{item?.head}}cm</text>
<text class="measurement-value">{{item?.bmi}}</text>
</view>
<view class="labels-row">
<view class="measurement-item">
<text class="measurement-label">身高</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.heightStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">体重</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.weightStatus]}}</text>
</view>
</view>
<view class="measurement-item" v-if="item?.head">
<text class="measurement-label">头围</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.headStatus]}}</text>
</view>
</view>
<view class="measurement-item">
<text class="measurement-label">BMI</text>
<view class="measurement-status normal">
<image class="status-icon" :src="`${$baseUrl}shengzhangTestResult/1001/duihaoIcon.png`" mode="aspectFit"></image>
<text>{{statusMap[item?.bmiStatus]}}</text>
</view>
</view>
</view>
</view>
<view class="growth-evaluation">
<text class="evaluation-text">{{item?.evaluation}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 专家咨询按钮 -->
<view class="expert-consult-btn" @click="consultExpert">
<image class="consult-bg" :src="`${$baseUrl}shengzhangTestResult/1001/zhuanjiazixunBtn.png`" mode="aspectFit"></image>
</view>
<!-- 生长曲线提示弹窗 -->
<ShengzhangQuxianTipsPopup
:visible="showTipsPopup"
@close="closeTipsPopup"
/>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad, onShareAppMessage } from "@dcloudio/uni-app";
import { useShengzhangStore } from '../../stores/shengzhangResult.js';
import { formatDate, jump, JumpType } from '../../utils/index.js';
import { getGrowthHistoryList,getGrowthAssessmentDetail } from '../../api/shengzhangTools';
import ShengzhangQuxianTipsPopup from '../../components/shengzhangQuxianTipsPopup.vue';
import { useUserStore } from "@/stores/user";
import { getHealthField } from "@/api/common";
const isRecords = ref(false);
const shareText = ref('')
onLoad((options) => {
isRecords.value = options.isRecords;
// activeTab.value = isRecords.value ? 'history' : 'latest';
})
const showNoValBg = ref(false);
// 导航标签状态
const activeTab = ref('latest') // 默认显示最新内容
// 生长曲线标签状态
const activeCurveTab = ref('height')
// 弹窗状态
const showTipsPopup = ref(false)
// 滑动相关状态
const scrollLeft = ref(0)
const totalWidth = ref(1080) // 36个月 * 30px
// 滚动偏移量
const scrollOffset = ref(0) // X轴绘制元素的偏移量
// 触摸事件相关
const isDragging = ref(false)
const startX = ref(0)
const lastX = ref(0)
const canvasWidth = ref(400) // 可视区域宽度
const babyInfo = ref()
const analysisData = ref({
bmiPercentile: 0,
bmiStatus: "LOW",
headPercentile: 0,
headStatus: "LOW",
heightPercentile: 0,
heightStatus: "LOW",
weightPercentile: 0,
weightStatus: "LOW"
})
const assessmentData = ref({
})
const contentText = ref({
evaluation: "描述",
shareText: "描述",
summary: "描述"
});
const statusBarPercentileMap = {
NORMAL: 177,
SLIGHT_LOW: 110,
SLIGHT_HIGH: 247,
LOW: 43,
HIGH: 314
}
// 历史数据列表
const historyList = ref([])
const dateConvert = (date) => {
if(!date){
return '';
}
const dateArray = date.split('-');
return dateArray[0] + '年' + dateArray[1] + '月' + dateArray[2] + '日';
}
// 生长曲线数据
const curveData = ref([
])
const curveDataPostHeight = ref([]);
const curveDataPostWeight = ref([]);
const curveDataPostHead = ref([]);
// 生成0-36个月的数据点
/**
* @param {number} startValue - 起始值(0个月时的数值)
* @param {number} endValue - 结束值(36个月时的数值)
* @param {string} type - 数据类型('height'|'weight'|'head')
* @returns {Array} 包含36个月数据点的数组
*/
const generateCurveData = (startValue, endValue, type) => {
const data = []
for (let i = 0; i <= 36; i++) {
const value = startValue + (endValue - startValue) * (i / 36)
const point = { month: i }
point[type] = Math.round(value * 10) / 10
data.push(point)
}
return data
}
//获取宝宝曲线数据范围4条线,用于绘制曲线
const generateCurveData1 = (lineData, type, arrayName, minOrMax) =>{
const data = [];
lineData.forEach(element => {
const point = { month: element.monthAge }
point[type] = minOrMax === 'min' ? element?.[arrayName]?.min : element?.[arrayName]?.max
data.push(point);
});
return data;
}
// slightHighRange
// slightLowRange
// 标准生长曲线数据(多条线,延长到36个月)
const standardCurves = ref({
height: {
slightlyLow: generateCurveData1(curveDataPostHeight.value, 'height', 'slightLowRange', 'min'),
// normal: generateCurveData(50, 110, 'height'),
normal: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHeight.value, 'height', 'slightHighRange', 'max')
},
weight: {
slightlyLow: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightHighRange', 'max')
},
head: {
slightlyLow: generateCurveData1(curveDataPostHead.value, 'head', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHead.value, 'head', 'slightHighRange', 'max')
}
})
const statusMap = {
NORMAL: '正常',
SLIGHT_LOW: '略低',
SLIGHT_HIGH: '略高',
LOW: '偏低',
HIGH: '偏高'
}
// 切换导航标签
/**
* @param {string} tab - 要切换的标签名称('latest'|'history')
*/
const switchTab = (tab) => {
activeTab.value = tab
console.log('切换到标签:', tab)
// if(tab === 'latest' && isRecords.value){
// switchCurveTab('height');
// }
}
// 选择历史记录项
/**
* @param {Object} item - 选中的历史记录项
*/
const selectHistoryItem = (item) => {
console.log('选择历史记录:', item)
// 这里可以添加跳转到详情页或更新当前显示数据的逻辑
uni.showToast({
title: '已选择历史记录',
icon: 'success'
})
}
// 切换生长曲线标签
/**
* @param {string} tab - 要切换的曲线类型('height'|'weight'|'head')
*/
const switchCurveTab = (tab) => {
activeCurveTab.value = tab
console.log('切换到曲线标签:', tab)
// 根据不同的标签更新曲线数据
if (tab === 'height') {
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHeight.userDataPoints,'height');
} else if (tab === 'weight') {
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoWeight.userDataPoints,'weight');
} else if (tab === 'head') {
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHead.userDataPoints,'head');
}
// 重新绘制曲线
setTimeout(() => {
drawGrowthCurve()
}, 100)
}
const curveDataConvert = (curveData,type) => {
const data = [];
curveData.forEach(element => {
const point = { month: element.monthAge }
point[type] = element?.["value"]
data.push(point);
});
console.log('curveDataConvert data=', data);
return data;
}
//获取当前宝宝曲线数据,用于绘制曲线
const generateCurveData2 = (lineData, type, arrayName, minOrMax) =>{
const data = [];
lineData.forEach(element => {
const point = { month: element.monthAge }
point[type] = minOrMax === 'min' ? element?.[arrayName]?.min : element?.[arrayName]?.max
data.push(point);
});
return data;
}
// 获取Y轴标签
/**
* @returns {string} 根据当前曲线类型返回对应的Y轴标签
*/
const getYAxisLabel = () => {
if (activeCurveTab.value === 'height') {
return '身高 (cm)'
} else if (activeCurveTab.value === 'weight') {
return '体重 (kg)'
} else if (activeCurveTab.value === 'head') {
return '头围 (cm)'
}
return '身高 (cm)'
}
// 获取Y轴刻度
/**
* @returns {Array<number>} 根据当前曲线类型返回对应的Y轴刻度数组
*/
const getYTicks = () => {
if (activeCurveTab.value === 'height') {
return [40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170]
} else if (activeCurveTab.value === 'weight') {
return [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
} else if (activeCurveTab.value === 'head') {
return [20, 25, 30, 35, 40, 45, 50, 55, 60]
}
return [40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170]
}
// 显示生长曲线提示
/**
* 显示生长曲线说明弹窗
*/
const showCurveTips = () => {
console.log('显示生长曲线提示')
showTipsPopup.value = true
}
// 关闭生长曲线提示弹窗
/**
* 关闭生长曲线说明弹窗
*/
const closeTipsPopup = () => {
showTipsPopup.value = false
}
// 专家咨询
/**
* 处理专家咨询按钮点击事件
*/
const consultExpert = async () => {
console.log('专家在线咨询')
const res = await getHealthField();
if (!res.success) {
uni.showToast({
title: "获取健康字段失败",
icon: "none",
});
return;
}
const { sign, timestamp, appId, partnerUserId, env } = res.data;
jump({
type: JumpType.MINI,
url: "/pages/partner/redirect",
extra: {
appId: "wx81ecfb5aa3fb512f",
envVersion: env,
extraData: {
sign, // 参考 4.请求参数
timestamp, // 参考 4.请求参数
appId, // 参考 4.请求参数
partnerUserId, // 参考 4.请求参数
targetApp:
"/h5/partner/shining-like-a-start/landing-free-consult?sysType=CRF",
},
},
});
}
// 首页组件逻辑
/**
* 处理返回按钮点击事件,尝试返回上一页或跳转到首页
*/
const backHandler = () => {
console.log('backHandler');
try {
uni.navigateBack({
success: () => {
console.log('返回成功')
},
fail: backFailHandler
})
} catch (error) {
console.log('error=', error)
jump({
type: JumpType.INNER,
url: "/pages/index/index"
})
}
}
const backFailHandler = () => {
console.log('backFailHandler');
}
onShareAppMessage(() => {
return {
title: shareText.value,
path: `/pages/shengzhangTestResult/shengzhangTestResult`,
imageUrl: ''
}
})
const shengzhangStore = useShengzhangStore();
// const headCircumference = ref(0);
onMounted(async () => {
//获取历史记录
const historyListData = await getGrowthHistoryList();
if(!historyListData.success || (!historyListData.data || historyListData.data.length == 0)){
showNoValBg.value = true;
return;
}
historyList.value = [];
if(historyListData.success){
historyListData.data.forEach(item => {
let itemData = {};
itemData.gender = item.babyInfo.gender;
itemData.age = item.babyInfo.monthAge;
itemData.testDate = formatDate(item.assessmentDate);
itemData.height = item.assessmentData.height;
itemData.weight = item.assessmentData.weight;
itemData.head = item.assessmentData.headCircumference;
itemData.bmi = item.assessmentData.bmi;
itemData.evaluation = item.evaluation;
itemData.id = item.id;//测评id
itemData.babyName = item.babyInfo.babyName;
itemData.bmiStatus = item.statusAnalysis.bmiStatus;
itemData.heightStatus = item.statusAnalysis.heightStatus;
itemData.weightStatus = item.statusAnalysis.weightStatus;
itemData.headStatus = item.statusAnalysis.headStatus;
historyList.value.push(itemData);
})
}
let shengzhangInfo = {};
if(isRecords.value){
if(historyListData.success){
if(historyListData.data && historyListData.data.length > 0){
shengzhangInfo = historyListData.data[0];
const detailData = await getGrowthAssessmentDetail(shengzhangInfo.id);
if(detailData.success){
shengzhangInfo = detailData.data;
}
}
}
const userStore = useUserStore();
const babyId = userStore.babyInfo?.content?.id;
const babyDataHeight = {
babyId: babyId,
curveType: 'HEIGHT',
startMonth: 0,
endMonth: 36,
};
await shengzhangStore.getGrowthCurveData(babyDataHeight);
const babyDataWeight = {
babyId: babyId,
curveType: 'WEIGHT',
startMonth: 0,
endMonth: 36,
};
await shengzhangStore.getGrowthCurveData(babyDataWeight);
const babyDataHead = {
babyId: babyId,
curveType: 'HEAD',
startMonth: 0,
endMonth: 36,
};
await shengzhangStore.getGrowthCurveData(babyDataHead);
//默认展示身高
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHeight.userDataPoints,'height');
// 初始化绘制曲线
setTimeout(() => {
drawGrowthCurve()
}, 100)
}else{
shengzhangInfo = {...shengzhangStore.shengzhangInfo};
}
shareText.value = shengzhangInfo?.content?.shareText;
console.log('shareText.value=', shareText.value);
//分析结果处理
const data = {...shengzhangInfo.babyInfo};
data.assessmentDate = formatDate(shengzhangInfo.assessmentDate);
babyInfo.value = data;
analysisData.value = shengzhangInfo.analysis;
assessmentData.value = shengzhangInfo.assessmentData;
// headCircumference.value = assessmentData.headCircumference;
// headCircumference.value = null;
contentText.value = shengzhangInfo.content;
//生长曲线处理
curveDataPostHeight.value = shengzhangStore.getGrowthCurveDataInfoHeight.curveData;
curveDataPostWeight.value = shengzhangStore.getGrowthCurveDataInfoWeight.curveData;
curveDataPostHead.value = shengzhangStore.getGrowthCurveDataInfoHead.curveData;
console.log('curveDataPostHeight.value=', curveDataPostHeight.value);
standardCurves.value = {
height: {
// slightlyLow: generateCurveData(45, 105, 'height'),
// normal: generateCurveData(50, 110, 'height'),
slightlyLow: generateCurveData1(curveDataPostHeight.value, 'height', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHeight.value, 'height', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHeight.value, 'height', 'slightHighRange', 'max'),
// slightlyHigh: generateCurveData(55, 115, 'height')slightHighRange
},
weight: {
// slightlyLow: generateCurveData(2.5, 15.0, 'weight'),
// normal: generateCurveData(3.0, 16.5, 'weight'),
// normal2: generateCurveData(3.2, 17.0, 'weight'), // 新增的a8e6cf颜色曲线
// slightlyHigh: generateCurveData(3.5, 18.0, 'weight')
slightlyLow: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostWeight.value, 'weight', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostWeight.value, 'weight', 'slightHighRange', 'max'),
},
head: {
// slightlyLow: generateCurveData(32, 52, 'head'),
// normal: generateCurveData(34, 54, 'head'),
// normal2: generateCurveData(35, 55, 'head'), // 新增的a8e6cf颜色曲线
// slightlyHigh: generateCurveData(36, 56, 'head')
slightlyLow: generateCurveData1(curveDataPostHead.value, 'head', 'slightLowRange', 'min'),
normal: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'min'),
normal2: generateCurveData1(curveDataPostHead.value, 'head', 'normalRange', 'max'), // 新增的a8e6cf颜色曲线
slightlyHigh: generateCurveData1(curveDataPostHead.value, 'head', 'slightHighRange', 'max'),
}
};
// 初始化绘制曲线
setTimeout(() => {
drawGrowthCurve()
}, 100)
})
// 绘制生长曲线
/**
* 绘制完整的生长曲线图表,包括坐标轴、标准曲线和宝宝记录曲线
*/
const drawGrowthCurve = () => {
const query = uni.createSelectorQuery()
query.select('.curve-canvas').boundingClientRect((rect) => {
if (rect) {
const ctx = uni.createCanvasContext('growthCurve')
const width = rect.width
const height = rect.height
canvasWidth.value = width // 更新可视区域宽度
// 清空画布
ctx.clearRect(0, 0, width, height)
// 设置画布边距
const margin = { top: 20, right: 20, bottom: 25, left:30 } // Y轴宽度
const chartWidth = totalWidth.value - margin.left - margin.right
const chartHeight = height - margin.top - margin.bottom
// 获取当前数据类型
const currentType = activeCurveTab.value
const currentCurves = standardCurves.value[currentType]
// 绘制滚动图层(X轴和图表内容)
drawScrollableLayer(ctx, width, height, margin, chartWidth, chartHeight, currentType)
// 绘制固定图层(Y轴)
drawFixedLayer(ctx, width, height, margin, chartHeight, currentType)
ctx.draw()
}
}).exec()
}
// 绘制坐标轴
/**
* @param {Object} ctx - Canvas上下文对象
* @param {number} width - Canvas总宽度
* @param {number} height - Canvas总高度
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawFixedLayer = (ctx, width, height, margin, chartHeight, type) => {
// console.log('drawFixedLayer', width, height, margin, chartHeight, type);
// 绘制Y轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
ctx.setLineWidth(1)
ctx.moveTo(margin.left, margin.top - 8)
ctx.lineTo(margin.left, height - margin.bottom)
ctx.stroke()
// 绘制Y轴箭头
ctx.beginPath()
ctx.moveTo(margin.left, margin.top - 16)
ctx.lineTo(margin.left - 4, margin.top + 8 - 16)
ctx.lineTo(margin.left + 4, margin.top + 8 - 16)
ctx.closePath()
ctx.fill()
// 绘制Y轴刻度和标签
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
const x = margin.left
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
// 刻度线
// ctx.beginPath()
// ctx.setStrokeStyle('#999')
// ctx.setLineWidth(1)
// ctx.moveTo(x, y)
// ctx.lineTo(x - 5, y)
// ctx.stroke()
// 刻度标签
ctx.setFillStyle('#000')
ctx.setFontSize(12)
ctx.setTextAlign('right')
ctx.fillText(tick.toString(), x - 10, y + 4)
})
}
// 绘制标准曲线
/**
* @param {Object} ctx - Canvas上下文对象
* @param {Object} curves - 标准曲线数据对象 {slightlyLow, normal, slightlyHigh}
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawStandardCurves = (ctx, curves, margin, chartWidth, chartHeight, type) => {
// 绘制略低曲线 - 黄色
drawCurve(ctx, curves.slightlyLow, margin, chartWidth, chartHeight, type, '#ffeaa7', 2)
// 绘制正常曲线 - 浅绿色
drawCurve(ctx, curves.normal, margin, chartWidth, chartHeight, type, '#a8e6cf', 2)
// 绘制新增的正常曲线2 - 浅绿色(不重合)
drawCurve(ctx, curves.normal2, margin, chartWidth, chartHeight, type, '#a8e6cf', 2)
// 绘制略高曲线 - 紫色
drawCurve(ctx, curves.slightlyHigh, margin, chartWidth, chartHeight, type, '#d4a5f5', 2)
}
// 绘制单条曲线
/**
* @param {Object} ctx - Canvas上下文对象
* @param {Array} data - 曲线数据点数组,每个元素包含 {month, height/weight/head}
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
* @param {string} color - 曲线颜色(十六进制颜色值)
* @param {number} lineWidth - 曲线线宽
*/
const drawCurve = (ctx, data, margin, chartWidth, chartHeight, type, color, lineWidth) => {
ctx.beginPath()
ctx.setStrokeStyle(color)
ctx.setLineWidth(lineWidth)
data.forEach((point, index) => {
const x = margin.left + (point.month / 36) * chartWidth
let y = 0
// 根据不同的数据类型计算y坐标
if (type === 'height') {
y = margin.top + (1 - (point.height - 40) / 80) * chartHeight
} else if (type === 'weight') {
y = margin.top + (1 - (point.weight - 2) / 16) * chartHeight
} else if (type === 'head') {
y = margin.top + (1 - (point.head - 30) / 30) * chartHeight
}
if (index === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
})
ctx.stroke()
}
// 绘制宝宝记录曲线
/**
* @param {Object} ctx - Canvas上下文对象
* @param {Array} data - 宝宝记录数据点数组,每个元素包含 {month, height/weight/head}
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawBabyCurve = (ctx, data, margin, chartWidth, chartHeight, type) => {
// 绘制折线
ctx.beginPath()
ctx.setStrokeStyle('#8b4513')
ctx.setLineWidth(1)
data.forEach((point, index) => {
const x = margin.left + (point.month / 36) * chartWidth
let y = 0
// 根据不同的数据类型计算y坐标
if (type === 'height') {
y = margin.top + (1 - (point.height - 40) / 80) * chartHeight
} else if (type === 'weight') {
y = margin.top + (1 - (point.weight - 2) / 16) * chartHeight
} else if (type === 'head') {
y = margin.top + (1 - (point.head - 30) / 30) * chartHeight
}
if (index === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
})
ctx.stroke()
// 绘制数据点
data.forEach((point) => {
const x = margin.left + (point.month / 36) * chartWidth
let y = 0
// 根据不同的数据类型计算y坐标
if (type === 'height') {
y = margin.top + (1 - (point.height - 40) / 80) * chartHeight
} else if (type === 'weight') {
y = margin.top + (1 - (point.weight - 2) / 16) * chartHeight
} else if (type === 'head') {
y = margin.top + (1 - (point.head - 30) / 30) * chartHeight
}
ctx.beginPath()
ctx.setFillStyle('#8b4513')
ctx.arc(x, y, 6, 0, 2 * Math.PI)
ctx.fill()
})
}
// 绘制滚动图层
/**
* @param {Object} ctx - Canvas上下文对象
* @param {number} width - Canvas总宽度
* @param {number} height - Canvas总高度
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawScrollableLayer = (ctx, width, height, margin, chartWidth, chartHeight, type) => {
// console.log('drawScrollableLayer', width, height, margin, chartWidth, chartHeight, type);
// 保存当前上下文状态
ctx.save()
// 设置裁剪区域,只显示可视区域
ctx.beginPath()
ctx.rect(margin.left, 0, width - margin.left - margin.right, height)
ctx.clip()
// 应用滚动偏移
ctx.translate(-scrollOffset.value, 0)
// 绘制X轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
ctx.setLineWidth(2)
ctx.moveTo(margin.left, height - margin.bottom)
ctx.lineTo(totalWidth.value - margin.right, height - margin.bottom)
ctx.stroke()
// 绘制X轴箭头
ctx.beginPath()
ctx.moveTo(totalWidth.value - margin.right, height - margin.bottom)
ctx.lineTo(totalWidth.value - margin.right - 8, height - margin.bottom - 4)
ctx.lineTo(totalWidth.value - margin.right - 8, height - margin.bottom + 4)
ctx.closePath()
ctx.setFillStyle('#000')
ctx.fill()
// 先绘制水平网格线
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
if(index != 0){
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
// 绘制水平网格线
ctx.beginPath()
ctx.setStrokeStyle('#faf2e7')
ctx.setLineWidth(1)
ctx.moveTo(margin.left, y)
ctx.lineTo(totalWidth.value - margin.right, y)
ctx.stroke()
}
})
// 绘制X轴刻度(每个月都显示)
const xTicks = []
for (let i = 0; i <= 36; i++) {
xTicks.push(i)
}
xTicks.forEach((tick) => {
const x = margin.left + (tick / 36) * chartWidth + 4
const y = height - margin.bottom
// 刻度标签
ctx.setFillStyle('#000')
ctx.setFontSize(12)
ctx.setTextAlign('center')
ctx.fillText(tick.toString(), x, y + 20)
})
// 绘制坐标轴标签
// ctx.setFillStyle('#000')
// ctx.setFontSize(14)
// ctx.setTextAlign('center')
// ctx.fillText('月龄', totalWidth.value / 2, height - 10)
// 绘制标准曲线
drawStandardCurves(ctx, standardCurves.value[type], margin, chartWidth, chartHeight, type)
// 绘制宝宝记录曲线
drawBabyCurve(ctx, curveData.value, margin, chartWidth, chartHeight, type)
// 恢复上下文状态
ctx.restore()
}
// 获取X轴刻度
/**
* @returns {Array<number>} 返回0-36个月的X轴刻度数组
*/
// const getXTicks = () => {
// const ticks = []
// for (let i = 0; i <= 36; i++) {
// ticks.push(i)
// }
// return ticks
// }
// 处理滑动事件
/**
* @param {Object} e - 滑动事件对象
* @param {number} e.detail.scrollLeft - 当前滑动位置
*/
const onScroll = (e) => {
scrollLeft.value = e.detail.scrollLeft
}
// 设置滚动偏移量
/**
* @param {number} offset - X轴偏移量
*/
const setScrollOffset = (offset) => {
scrollOffset.value = offset
drawGrowthCurve() // 重新绘制图表
}
// 触摸开始事件
const onTouchStart = (e) => {
isDragging.value = true
startX.value = e.touches[0].clientX
lastX.value = e.touches[0].clientX
}
// 触摸移动事件
const onTouchMove = (e) => {
if (!isDragging.value) return
const currentX = e.touches[0].clientX
const deltaX = lastX.value - currentX
// 计算新的滚动偏移量
const newOffset = scrollOffset.value + deltaX
// const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
// 限制滚动范围,允许一定的弹性效果
// if (newOffset >= -50 && newOffset <= maxOffset + 50) {
// scrollOffset.value = newOffset
// drawGrowthCurve() // 重新绘制图表
// }
const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
if (newOffset <= 0) {
scrollOffset.value = 0;
} else if (newOffset >= 767){//maxOffset) {
scrollOffset.value = 767//maxOffset
}else{
scrollOffset.value = newOffset;
}
drawGrowthCurve();
lastX.value = currentX
}
// 触摸结束事件
const onTouchEnd = () => {
isDragging.value = false
return;
// 确保滚动位置在有效范围内
const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
if (scrollOffset.value < 0) {
scrollOffset.value = 0
drawGrowthCurve()
} else if (scrollOffset.value > maxOffset) {
scrollOffset.value = maxOffset
drawGrowthCurve()
}
}
</script>
<style lang="less" scoped>
.shengzhang-test-result-container {
width: 100%;
height: 100%;
// box-sizing: border-box;
position: absolute;
// overflow-x: hidden;
// overflow-y: auto;
background-color: #fef7f2;
.result-bg{
top: 0rpx;
width: 100%;
height: 2700rpx;
position: absolute;
.result-bg-img0{
position: absolute;
top: 0rpx;
width: 100%;
height: 1300rpx;
}
.result-bg-img1{
position: absolute;
top: 1300rpx;
width: 100%;
height: 1400rpx;
}
}
.no-val-bg{
top: 0rpx;
width: 100%;
height: 100%;
position: absolute;
.no-val-bg-img{
position: relative;
top: 0rpx;
width: 100%;
// height: 100%;
bottom: 0rpx;
}
}
// 内容容器
.content-wrapper {
padding-left: 30rpx;
padding-right: 30rpx;
}
// 最新内容容器
.latest-content {
display: none;
&.active {
display: block;
}
}
// 历史内容容器
.history-content {
padding-left: 30rpx;
padding-right: 30rpx;
display: none;
background-color: #fef7f2;
padding-bottom: 10rpx;
&.active {
display: block;
}
.history-list {
margin-top: 46rpx;
margin-bottom: 200rpx;
}
.history-item {
margin-bottom: 30rpx;
}
.history-card {
background-color: #fff;
border-radius: 24rpx;
padding: 50rpx 35rpx 35rpx 35rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
}
}
.baby-basic-info {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.gender {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.age {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.test-date {
font-size: 24rpx;
color: #000;
}
}
.measurement-summary {
display: flex;
flex-direction: column;
margin-bottom: 30rpx;
.values-row {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
.measurement-value {
font-size: 32rpx;
color: #333;
font-weight: bold;
flex: 1;
text-align: center;
}
}
.labels-row {
display: flex;
justify-content: space-between;
.measurement-item {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
justify-content: center;
.measurement-label {
font-size: 24rpx;
color: #666;
margin-right: 8rpx;
}
.measurement-status {
font-size: 22rpx;
color: #52c41a;
display: flex;
align-items: center;
.status-icon {
width: 16rpx;
height: 16rpx;
margin-right: 6rpx;
}
}
}
}
}
.growth-evaluation {
.evaluation-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
}
}
// 返回按钮
.back-btn {
position: absolute;
top: 119rpx;
left: 30rpx;
width: 29rpx;
height: 29rpx;
}
.title{
position: absolute;
top: 112rpx;
font-size: 34rpx;
font-weight: 500;
width: 100%;
text-align: center;
}
// 顶部导航标签
.nav-tabs {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-top: 202rpx;
width: 260rpx;
margin-left: 5rpx;
.tab-item {
width: 116rpx;
height: 51rpx;
// padding: 20rpx 40rpx;
margin-right: 30rpx;
border-radius: 25rpx;
background-color: #fffbed;
transition: all 0.3s ease;
&.active {
background-color: #b27c1e;
.tab-text {
color: #ffffff;
}
}
.tab-text {
font-size: 28rpx;
color: #b27c1e;
font-weight: 500;
align-items: center;
justify-content: center;
display: flex;
width: 100%;
height: 100%;
}
}
// .tab-decoration {
// position: absolute;
// right: 20rpx;
// top: 50%;
// transform: translateY(-50%);
// .star {
// font-size: 20rpx;
// color: #b27c1e;
// margin-left: 10rpx;
// opacity: 0.6;
// }
// }
}
// 宝宝信息卡片
.baby-info-card {
background-color: #fff;
border-radius: 24rpx;
padding: 50rpx 35rpx 35rpx 35rpx;
margin-top: 46rpx;
// height: 100%;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
}
}
.baby-basic-info {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.gender {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.age {
font-size: 24rpx;
color: #000;
margin-right: 20rpx;
}
.test-date {
font-size: 24rpx;
color: #000;
}
}
.measurement-summary {
display: flex;
flex-direction: column;
margin-bottom: 30rpx;
.values-row {
display: flex;
justify-content: space-between;
margin-bottom: 15rpx;
.measurement-value {
font-size: 32rpx;
color: #333;
font-weight: bold;
flex: 1;
text-align: center;
}
}
.labels-row {
display: flex;
justify-content: space-between;
.measurement-item {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
justify-content: center;
.measurement-label {
font-size: 24rpx;
color: #666;
margin-right: 8rpx;
}
.measurement-status {
font-size: 22rpx;
color: #52c41a;
display: flex;
align-items: center;
.status-icon {
width: 16rpx;
height: 16rpx;
margin-right: 6rpx;
}
}
}
}
}
.growth-evaluation {
.evaluation-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
}
// 生长情况卡片
.growth-status-card {
background-color: #fff;
border-radius: 24rpx;
padding: 50rpx 35rpx 15rpx 35rpx;
margin-top: 40rpx;
// box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #000;
font-weight: bold;
}
}
.legend {
display: flex;
justify-content: space-between;
margin-top: 46rpx;
.legend-item {
display: flex;
align-items: center;
.legend-color {
width: 20rpx;
height: 20rpx;
border-radius: 4rpx;
margin-right: 8rpx;
&.too-low {
background-color: #ffede0;
}
&.slightly-low {
background-color: #fde0a5;
}
&.normal {
background-color: #89caa2;
}
&.slightly-high {
background-color: #f3d1e9;
}
&.too-high {
background-color: #a78dbc;
}
}
.legend-text {
font-size: 22rpx;
color: #000;
}
}
}
.measurement-bars {
margin-top: 26rpx;
.bar-item {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
.value-triangle-container {
display: flex;
flex-direction: column;
width: 150rpx;
align-items: center;
.bar-value {
font-size: 24rpx;
color: #b27c1e;
}
.triangle {
// position: relative;
top:8rpx;
// margin-left: 20rpx;
width: 20rpx;
height: 20rpx;
// align-self: center;
}
}
.bar-row {
display: flex;
align-items: center;
justify-content: space-between;
.measurement-label {
font-size: 28rpx;
color: #000;
width: 80rpx;
// margin-top: 3rpx;
}
.value-bar {
flex: 1;
height: 30rpx;
}
.bar-percentage {
font-size: 24rpx;
color: #000;
width: 200rpx;
text-align: right;
}
}
}
}
}
// 生长曲线卡片
.growth-curve-card {
background-color: #fff;
border-radius: 24rpx;
padding: 30rpx;
margin-top: 40rpx;
// box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
position: relative;
.card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.name-icon,
.status-icon {
width: 32rpx;
height: 32rpx;
margin-right: 10rpx;
}
.card-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
}
}
.curve-tabs {
display: flex;
justify-content: space-around;
// justify-items: center;
width: 466rpx;
height: 141rpx;
margin-bottom: 27rpx;
margin-top: 27rpx;
margin-left: 78rpx;
background-color: #f6f8fa;
border-radius: 70rpx;
.curve-tab {
margin-top: 8rpx;
display: flex;
align-items: center;
justify-content: center;
width: 132rpx;
height: 131rpx;
border-radius: 50%;
// transition: all 0.3s ease;
// background-color: #ffffff;
// box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
position: relative;
&.active {
// background-color: #e0e0e0;
// box-shadow: none;
}
.tab-icon {
width: 100%;
height: 100%;
}
}
}
.graph-legend {
display: flex;
justify-content: space-around;
.legend-item {
display: flex;
align-items: center;
.legend-color {
width: 20rpx;
height: 20rpx;
border-radius: 4rpx;
margin-right: 6rpx;
&.slightly-low {
background-color: #fde0a5; // 黄色
}
&.normal {
background-color: #89caa2; // 浅绿色
}
&.normal2 {
background-color: #a8e6cf; // 浅绿色(第二条线)
}
&.slightly-high {
background-color: #f3d1e9; // 紫色
}
&.baby-record {
background-color: #b27c1e; // 棕色
}
}
.legend-text {
font-size: 20rpx;
color: #000;
}
}
}
.graph-title-y{
position: relative;
margin-top: 0rpx;
margin-left: 20rpx;
.graph-title-text{
font-size: 20rpx;
color: #000;
}
}
.graph-container {
position: relative;
height: 400rpx;
background-color: #fff;
border-radius: 10rpx;
overflow: hidden;
.curve-canvas {
width: 100%;
height: 100%;
display: block;
}
}
.graph-title-x{
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
.graph-title-text{
font-size: 20rpx;
color: #000;
}
}
}
.curve-tips {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-top: 31rpx;
margin-bottom: 60rpx;
width: 100%;
height: 24rpx;
.tips-icon{
width: 198rpx;
height: 100%;
}
}
// 专家咨询按钮
.expert-consult-btn {
position: fixed;
bottom: 60rpx;
left: 0;
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
.consult-bg {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
}
}
</style>
\ No newline at end of file
......@@ -219,11 +219,10 @@
<text class="graph-title-text">{{getYAxisLabel()}}</text>
</view>
<view class="graph-container">
<scroll-view class="graph-scroll" scroll-x="true" :scroll-left="scrollLeft" @scroll="onScroll">
<view class="graph-content" :style="{ width: totalWidth + 'px' }">
<canvas class="curve-canvas" canvas-id="growthCurve" :style="{ width: totalWidth + 'px', height: '100%' }"></canvas>
</view>
</scroll-view>
<canvas class="curve-canvas" canvas-id="growthCurve" :style="{ width: totalWidth + 'px', height: '100%' }" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></canvas>
</view>
<view class="graph-title-x">
<text class="graph-title-text">月龄</text>
</view>
</view>
......@@ -309,7 +308,6 @@
<script setup>
import { ref, onMounted } from 'vue'
import { onLoad, onShareAppMessage } from "@dcloudio/uni-app";
import { useShengzhangStore } from '../../stores/shengzhangResult.js';
import { formatDate, jump, JumpType } from '../../utils/index.js';
......@@ -340,7 +338,17 @@ const showTipsPopup = ref(false)
// 滑动相关状态
const scrollLeft = ref(0)
const totalWidth = ref(1080) // 36个月 * 10px
const totalWidth = ref(1080) // 36个月 * 30px
// 滚动偏移量
const scrollOffset = ref(0) // X轴绘制元素的偏移量
// 触摸事件相关
const isDragging = ref(false)
const startX = ref(0)
const lastX = ref(0)
const canvasWidth = ref(400) // 可视区域宽度
const babyInfo = ref()
const analysisData = ref({
......@@ -722,9 +730,9 @@ onMounted(async () => {
//默认展示身高
curveData.value = curveDataConvert(shengzhangStore.getGrowthCurveDataInfoHeight.userDataPoints,'height');
// 初始化绘制曲线
// setTimeout(() => {
// drawGrowthCurve()
// }, 100)
setTimeout(() => {
drawGrowthCurve()
}, 100)
}else{
......@@ -811,28 +819,26 @@ const drawGrowthCurve = () => {
const ctx = uni.createCanvasContext('growthCurve')
const width = rect.width
const height = rect.height
canvasWidth.value = width // 更新可视区域宽度
// 清空画布
ctx.clearRect(0, 0, width, height)
// 设置画布边距
const margin = { top: 20, right: 20, bottom: 25, left: 30 }
const chartWidth = width - margin.left - margin.right
const margin = { top: 20, right: 20, bottom: 25, left:30 } // Y轴宽度
const chartWidth = totalWidth.value - margin.left - margin.right
const chartHeight = height - margin.top - margin.bottom
// 获取当前数据类型
const currentType = activeCurveTab.value
const currentCurves = standardCurves.value[currentType]
// 绘制坐标轴
drawAxes(ctx, width, height, margin, chartWidth, chartHeight, currentType)
// 绘制标准曲线
drawStandardCurves(ctx, currentCurves, margin, chartWidth, chartHeight, currentType)
// 绘制宝宝记录曲线
drawBabyCurve(ctx, curveData.value, margin, chartWidth, chartHeight, currentType)
// 绘制滚动图层(X轴和图表内容)
drawScrollableLayer(ctx, width, height, margin, chartWidth, chartHeight, currentType)
// 绘制固定图层(Y轴)
drawFixedLayer(ctx, width, height, margin, chartHeight, currentType)
ctx.draw()
}
}).exec()
......@@ -844,34 +850,11 @@ const drawGrowthCurve = () => {
* @param {number} width - Canvas总宽度
* @param {number} height - Canvas总高度
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawAxes = (ctx, width, height, margin, chartWidth, chartHeight, type) => {
console.log('drawAxes', width, height, margin, chartWidth, chartHeight, type);
// 绘制X轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
ctx.setLineWidth(2)
ctx.moveTo(margin.left, height - margin.bottom)
ctx.lineTo(width - margin.right, height - margin.bottom)
ctx.stroke()
// 先绘制水平网格线
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
// 绘制水平网格线
ctx.beginPath()
ctx.setStrokeStyle('#faf2e7')
ctx.setLineWidth(1)
ctx.moveTo(margin.left, y)
ctx.lineTo(width - margin.right, y)
ctx.stroke()
})
const drawFixedLayer = (ctx, width, height, margin, chartHeight, type) => {
// console.log('drawFixedLayer', width, height, margin, chartHeight, type);
// 绘制Y轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
......@@ -880,15 +863,6 @@ const drawAxes = (ctx, width, height, margin, chartWidth, chartHeight, type) =>
ctx.lineTo(margin.left, height - margin.bottom)
ctx.stroke()
// 绘制X轴箭头
ctx.beginPath()
ctx.moveTo(width - margin.right, height - margin.bottom)
ctx.lineTo(width - margin.right - 8, height - margin.bottom - 4)
ctx.lineTo(width - margin.right - 8, height - margin.bottom + 4)
ctx.closePath()
ctx.setFillStyle('#000')
ctx.fill()
// 绘制Y轴箭头
ctx.beginPath()
ctx.moveTo(margin.left, margin.top - 16)
......@@ -897,29 +871,8 @@ const drawAxes = (ctx, width, height, margin, chartWidth, chartHeight, type) =>
ctx.closePath()
ctx.fill()
// 绘制X轴刻度(每个月都显示)
const xTicks = []
for (let i = 0; i <= 36; i++) {
xTicks.push(i)
}
xTicks.forEach((tick) => {
const x = margin.left + (tick / 36) * chartWidth
const y = height - margin.bottom
// 刻度线
// ctx.beginPath()
// ctx.setStrokeStyle('#000')
// ctx.setLineWidth(1)
// ctx.moveTo(x, y)
// ctx.lineTo(x, y) // 缩短为原来的30%
// ctx.stroke()
// 刻度标签
ctx.setFillStyle('#000')
ctx.setFontSize(12)
ctx.setTextAlign('center')
ctx.fillText(tick.toString(), x, y + 20)
})
// 绘制Y轴刻度和标签
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
const x = margin.left
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
......@@ -939,20 +892,7 @@ const drawAxes = (ctx, width, height, margin, chartWidth, chartHeight, type) =>
ctx.fillText(tick.toString(), x - 10, y + 4)
})
// 绘制坐标轴标签
ctx.setFillStyle('#000')
ctx.setFontSize(14)
ctx.setTextAlign('center')
ctx.fillText('月龄', width / 2, height - 10)
// ctx.setTextAlign('center')
// ctx.setFontSize(14)
// const yLabel = getYAxisLabel()
// ctx.save()
// ctx.translate(20, height / 2)
// // ctx.rotate(-Math.PI / 2)
// ctx.fillText(yLabel, 0, -100)
// ctx.restore()
}
// 绘制标准曲线
......@@ -1075,6 +1015,95 @@ const drawBabyCurve = (ctx, data, margin, chartWidth, chartHeight, type) => {
})
}
// 绘制滚动图层
/**
* @param {Object} ctx - Canvas上下文对象
* @param {number} width - Canvas总宽度
* @param {number} height - Canvas总高度
* @param {Object} margin - 边距对象 {top, right, bottom, left}
* @param {number} chartWidth - 图表区域宽度
* @param {number} chartHeight - 图表区域高度
* @param {string} type - 数据类型('height'|'weight'|'head')
*/
const drawScrollableLayer = (ctx, width, height, margin, chartWidth, chartHeight, type) => {
// console.log('drawScrollableLayer', width, height, margin, chartWidth, chartHeight, type);
// 保存当前上下文状态
ctx.save()
// 设置裁剪区域,只显示可视区域
ctx.beginPath()
ctx.rect(margin.left, 0, width - margin.left - margin.right, height)
ctx.clip()
// 应用滚动偏移
ctx.translate(-scrollOffset.value, 0)
// 绘制X轴
ctx.beginPath()
ctx.setStrokeStyle('#000')
ctx.setLineWidth(2)
ctx.moveTo(margin.left, height - margin.bottom)
ctx.lineTo(totalWidth.value - margin.right, height - margin.bottom)
ctx.stroke()
// 绘制X轴箭头
ctx.beginPath()
ctx.moveTo(totalWidth.value - margin.right, height - margin.bottom)
ctx.lineTo(totalWidth.value - margin.right - 8, height - margin.bottom - 4)
ctx.lineTo(totalWidth.value - margin.right - 8, height - margin.bottom + 4)
ctx.closePath()
ctx.setFillStyle('#000')
ctx.fill()
// 先绘制水平网格线
const yTicks = getYTicks()
yTicks.forEach((tick, index) => {
if(index != 0){
const y = margin.top + (1 - index / (yTicks.length - 1)) * chartHeight
// 绘制水平网格线
ctx.beginPath()
ctx.setStrokeStyle('#faf2e7')
ctx.setLineWidth(1)
ctx.moveTo(margin.left, y)
ctx.lineTo(totalWidth.value - margin.right, y)
ctx.stroke()
}
})
// 绘制X轴刻度(每个月都显示)
const xTicks = []
for (let i = 0; i <= 36; i++) {
xTicks.push(i)
}
xTicks.forEach((tick) => {
const x = margin.left + (tick / 36) * chartWidth + 4
const y = height - margin.bottom
// 刻度标签
ctx.setFillStyle('#000')
ctx.setFontSize(12)
ctx.setTextAlign('center')
ctx.fillText(tick.toString(), x, y + 20)
})
// 绘制坐标轴标签
// ctx.setFillStyle('#000')
// ctx.setFontSize(14)
// ctx.setTextAlign('center')
// ctx.fillText('月龄', totalWidth.value / 2, height - 10)
// 绘制标准曲线
drawStandardCurves(ctx, standardCurves.value[type], margin, chartWidth, chartHeight, type)
// 绘制宝宝记录曲线
drawBabyCurve(ctx, curveData.value, margin, chartWidth, chartHeight, type)
// 恢复上下文状态
ctx.restore()
}
// 获取X轴刻度
/**
* @returns {Array<number>} 返回0-36个月的X轴刻度数组
......@@ -1096,6 +1125,71 @@ const onScroll = (e) => {
scrollLeft.value = e.detail.scrollLeft
}
// 设置滚动偏移量
/**
* @param {number} offset - X轴偏移量
*/
const setScrollOffset = (offset) => {
scrollOffset.value = offset
drawGrowthCurve() // 重新绘制图表
}
// 触摸开始事件
const onTouchStart = (e) => {
isDragging.value = true
startX.value = e.touches[0].clientX
lastX.value = e.touches[0].clientX
}
// 触摸移动事件
const onTouchMove = (e) => {
if (!isDragging.value) return
const currentX = e.touches[0].clientX
const deltaX = lastX.value - currentX
// 计算新的滚动偏移量
const newOffset = scrollOffset.value + deltaX
// const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
// 限制滚动范围,允许一定的弹性效果
// if (newOffset >= -50 && newOffset <= maxOffset + 50) {
// scrollOffset.value = newOffset
// drawGrowthCurve() // 重新绘制图表
// }
const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
if (newOffset <= 0) {
scrollOffset.value = 0;
} else if (newOffset >= 767){//maxOffset) {
scrollOffset.value = 767//maxOffset
}else{
scrollOffset.value = newOffset;
}
drawGrowthCurve();
lastX.value = currentX
}
// 触摸结束事件
const onTouchEnd = () => {
isDragging.value = false
return;
// 确保滚动位置在有效范围内
const maxOffset = Math.max(0, totalWidth.value - canvasWidth.value)
if (scrollOffset.value < 0) {
scrollOffset.value = 0
drawGrowthCurve()
} else if (scrollOffset.value > maxOffset) {
scrollOffset.value = maxOffset
drawGrowthCurve()
}
}
</script>
<style lang="less" scoped>
......@@ -1634,8 +1728,8 @@ const onScroll = (e) => {
// justify-items: center;
width: 466rpx;
height: 141rpx;
margin-bottom: 37rpx;
margin-top: 37rpx;
margin-bottom: 27rpx;
margin-top: 27rpx;
margin-left: 78rpx;
background-color: #f6f8fa;
border-radius: 70rpx;
......@@ -1708,7 +1802,7 @@ const onScroll = (e) => {
}
.graph-title-y{
position: relative;
margin-top: 30rpx;
margin-top: 0rpx;
margin-left: 20rpx;
.graph-title-text{
font-size: 20rpx;
......@@ -1723,22 +1817,24 @@ const onScroll = (e) => {
border-radius: 10rpx;
overflow: hidden;
.graph-scroll {
width: 100%;
height: 100%;
white-space: nowrap;
}
.graph-content {
height: 100%;
display: inline-block;
}
.curve-canvas {
width: 100%;
height: 100%;
display: block;
}
}
.graph-title-x{
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
.graph-title-text{
font-size: 20rpx;
color: #000;
}
}
}
......@@ -1748,7 +1844,7 @@ const onScroll = (e) => {
display: flex;
align-items: center;
justify-content: center;
margin-top: 60rpx;
margin-top: 31rpx;
margin-bottom: 60rpx;
width: 100%;
height: 24rpx;
......
......@@ -427,11 +427,11 @@ onMounted(async () => {
console.log('babyIdsdfsdfsdfsdfsdfsdfdsfsdf=', babyId.value);
const a = {
"bgUrl": "my/babytest.png",
"bgUrl": "my/shengzhangTools.png",
"desc": "生长测评",
"link": {
"type": 1,
"url": "/pages/shengzhangTools/shengzhangTools?babyId="
"url": "/pages/shengzhangTools/shengzhangTools"
},
"title": "生长测评"
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment