Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
飞
飞鹤小程序
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
FH
飞鹤小程序
Commits
3341d188
Commit
3341d188
authored
Jul 31, 2025
by
王炽
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
66666
parent
74f73c4a
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
3952 additions
and
106 deletions
+3952
-106
.DS_Store
pages/shengzhangTestResult/.DS_Store
+0
-0
shengzhangTestResult-canvas-dom-scroll.vue
...hangTestResult/shengzhangTestResult-canvas-dom-scroll.vue
+1875
-0
shengzhangTestResult-canvas-scroll.vue
...engzhangTestResult/shengzhangTestResult-canvas-scroll.vue
+1875
-0
shengzhangTestResult.vue
pages/shengzhangTestResult/shengzhangTestResult.vue
+200
-104
My.vue
views/My.vue
+2
-2
No files found.
pages/shengzhangTestResult/.DS_Store
0 → 100644
View file @
3341d188
File added
pages/shengzhangTestResult/shengzhangTestResult-canvas-dom-scroll.vue
0 → 100644
View file @
3341d188
<
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
pages/shengzhangTestResult/shengzhangTestResult-canvas-scroll.vue
0 → 100644
View file @
3341d188
<
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
pages/shengzhangTestResult/shengzhangTestResult.vue
View file @
3341d188
...
...
@@ -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
)
// 绘制
宝宝记录曲线
draw
BabyCurve
(
ctx
,
curveData
.
value
,
margin
,
chartWidth
,
chartHeight
,
currentType
)
// 绘制
滚动图层(X轴和图表内容)
draw
ScrollableLayer
(
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:
3
7rpx;
margin-top:
3
7rpx;
margin-bottom:
2
7rpx;
margin-top:
2
7rpx;
margin-left: 78rpx;
background-color: #f6f8fa;
border-radius: 70rpx;
...
...
@@ -1708,7 +1802,7 @@ const onScroll = (e) => {
}
.graph-title-y{
position: relative;
margin-top:
3
0rpx;
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:
60
rpx;
margin-top:
31
rpx;
margin-bottom: 60rpx;
width: 100%;
height: 24rpx;
...
...
views/My.vue
View file @
3341d188
...
...
@@ -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"
:
"生长测评"
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment