This commit is contained in:
parent
af722a2bbc
commit
34a3f7bf1f
|
|
@ -64,6 +64,14 @@
|
|||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/pets/pet-weight",
|
||||
"style": {
|
||||
"navigationBarTitleText": "体重管理",
|
||||
"navigationBarBackgroundColor": "#FF8A80",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"path": "pages/pets/pet-chat-simple",
|
||||
|
|
|
|||
|
|
@ -40,9 +40,10 @@
|
|||
<text class="stat-number">{{ petInfo.companionDays || 0 }}</text>
|
||||
<text class="stat-label">陪伴天数</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-item clickable" @click="goToWeightManagement">
|
||||
<text class="stat-number">{{ petInfo.weight || '4.2kg' }}</text>
|
||||
<text class="stat-label">当前体重</text>
|
||||
<text class="stat-arrow">›</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">85</text>
|
||||
|
|
@ -359,6 +360,12 @@ export default {
|
|||
this.viewRecords()
|
||||
},
|
||||
|
||||
goToWeightManagement() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/pets/pet-weight?petId=${this.petId}&petName=${this.petInfo.name}`
|
||||
})
|
||||
},
|
||||
|
||||
shareProfile() {
|
||||
uni.showToast({
|
||||
title: '分享功能开发中',
|
||||
|
|
@ -555,6 +562,24 @@ export default {
|
|||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.stat-arrow {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32rpx;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,664 @@
|
|||
<template>
|
||||
<view class="weight-container">
|
||||
<!-- 当前体重状态卡片 -->
|
||||
<view class="current-weight-card">
|
||||
<view class="weight-header">
|
||||
<text class="pet-name">{{ petName }}的体重管理</text>
|
||||
<text class="last-update">最后更新:{{ lastUpdateTime }}</text>
|
||||
</view>
|
||||
|
||||
<view class="weight-display">
|
||||
<view class="current-weight">
|
||||
<text class="weight-value">{{ currentWeight }}</text>
|
||||
<text class="weight-unit">kg</text>
|
||||
</view>
|
||||
<view class="weight-status" :class="weightStatusClass">
|
||||
<text class="status-text">{{ weightStatusText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 体重对比仪表盘 -->
|
||||
<view class="comparison-dashboard">
|
||||
<view class="dashboard-header">
|
||||
<text class="dashboard-title">📊 体重变化对比</text>
|
||||
</view>
|
||||
|
||||
<view class="comparison-grid">
|
||||
<view class="comparison-item">
|
||||
<view class="comparison-icon">📈</view>
|
||||
<view class="comparison-content">
|
||||
<text class="comparison-label">较上周</text>
|
||||
<text class="comparison-value" :class="weeklyChangeClass">
|
||||
{{ weeklyChange }}
|
||||
</text>
|
||||
<text class="comparison-percent" :class="weeklyChangeClass">
|
||||
{{ weeklyPercent }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="comparison-item">
|
||||
<view class="comparison-icon">📊</view>
|
||||
<view class="comparison-content">
|
||||
<text class="comparison-label">较上月</text>
|
||||
<text class="comparison-value" :class="monthlyChangeClass">
|
||||
{{ monthlyChange }}
|
||||
</text>
|
||||
<text class="comparison-percent" :class="monthlyChangeClass">
|
||||
{{ monthlyPercent }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间维度切换 -->
|
||||
<view class="time-selector">
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">📅 时间范围</text>
|
||||
</view>
|
||||
<view class="time-tabs">
|
||||
<view
|
||||
class="time-tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
v-for="tab in timeTabs"
|
||||
:key="tab.key"
|
||||
@click="switchTimeRange(tab.key)"
|
||||
>
|
||||
<text class="tab-text">{{ tab.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 体重变化图表 -->
|
||||
<view class="chart-container">
|
||||
<view class="chart-header">
|
||||
<text class="chart-title">📈 {{ currentTimeRange }}体重趋势</text>
|
||||
</view>
|
||||
<qiun-data-charts
|
||||
type="line"
|
||||
:opts="chartOpts"
|
||||
:chartData="chartData"
|
||||
:canvas2d="true"
|
||||
:canvasId="'weightChart'"
|
||||
:canvas-id="'weightChart'"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- AI分析卡片 -->
|
||||
<view class="ai-analysis-card">
|
||||
<view class="analysis-header">
|
||||
<text class="analysis-title">🤖 AI健康分析</text>
|
||||
<view class="analysis-badge">
|
||||
<text class="badge-text">智能分析</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="analysis-content">
|
||||
<view class="analysis-section">
|
||||
<text class="section-title">📋 健康评估</text>
|
||||
<text class="section-content">{{ healthAssessment }}</text>
|
||||
</view>
|
||||
|
||||
<view class="analysis-section">
|
||||
<text class="section-title">📈 趋势分析</text>
|
||||
<text class="section-content">{{ trendAnalysis }}</text>
|
||||
</view>
|
||||
|
||||
<view class="analysis-section">
|
||||
<text class="section-title">💡 建议</text>
|
||||
<text class="section-content">{{ recommendations }}</text>
|
||||
</view>
|
||||
|
||||
<view class="analysis-section" v-if="medicalAdvice">
|
||||
<text class="section-title">⚠️ 医疗建议</text>
|
||||
<text class="section-content warning">{{ medicalAdvice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加体重记录按钮 -->
|
||||
<view class="add-weight-btn" @click="addWeightRecord">
|
||||
<text class="add-btn-text">+ 添加体重记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import weightManager from '@/utils/weightManager.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
petId: '',
|
||||
petName: '',
|
||||
petInfo: {},
|
||||
currentWeight: '4.2',
|
||||
lastUpdateTime: '2024-01-20 14:30',
|
||||
weightStatusText: '健康范围',
|
||||
weightStatusClass: 'healthy',
|
||||
|
||||
// 体重变化数据
|
||||
weeklyChange: '+0.1kg',
|
||||
weeklyPercent: '+2.4%',
|
||||
weeklyChangeClass: 'increase',
|
||||
monthlyChange: '+0.3kg',
|
||||
monthlyPercent: '+7.1%',
|
||||
monthlyChangeClass: 'increase',
|
||||
|
||||
// 时间维度选项
|
||||
activeTab: 'week',
|
||||
timeTabs: [
|
||||
{ key: 'week', label: '近一周' },
|
||||
{ key: 'month', label: '近一月' },
|
||||
{ key: 'year', label: '近一年' },
|
||||
{ key: 'all', label: '全部历史' }
|
||||
],
|
||||
currentTimeRange: '近一周',
|
||||
|
||||
// 图表配置
|
||||
chartOpts: {
|
||||
color: ["#FF8A80", "#64B5F6"],
|
||||
padding: [15, 15, 0, 15],
|
||||
enableScroll: false,
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
xAxis: {
|
||||
disableGrid: true,
|
||||
fontSize: 10,
|
||||
fontColor: "#666666"
|
||||
},
|
||||
yAxis: {
|
||||
gridType: "dash",
|
||||
dashLength: 2,
|
||||
fontSize: 10,
|
||||
fontColor: "#666666"
|
||||
},
|
||||
extra: {
|
||||
line: {
|
||||
type: "curve",
|
||||
width: 2,
|
||||
activeType: "hollow"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 图表数据
|
||||
chartData: {},
|
||||
|
||||
// AI分析内容
|
||||
healthAssessment: '根据小橘的品种(橘猫)、年龄(2岁)和性别(公),当前体重4.2kg处于健康范围内。成年橘猫的理想体重通常在3.5-5.5kg之间。',
|
||||
trendAnalysis: '近期体重呈稳定上升趋势,增长速度适中。这种增长模式符合健康成长规律,无需过度担心。',
|
||||
recommendations: '建议保持当前的饮食习惯,每日定时定量喂食。增加适量运动,如逗猫棒游戏15-20分钟。定期监测体重变化。',
|
||||
medicalAdvice: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.petId = options.petId || ''
|
||||
this.petName = options.petName || '宠物'
|
||||
this.loadPetInfo()
|
||||
this.loadWeightData()
|
||||
this.generateChartData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadPetInfo() {
|
||||
try {
|
||||
const pets = uni.getStorageSync('pets') || []
|
||||
this.petInfo = pets.find(pet => pet.id == this.petId) || {
|
||||
id: this.petId,
|
||||
name: this.petName,
|
||||
breed: '橘猫',
|
||||
age: 2,
|
||||
gender: '公'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载宠物信息失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
loadWeightData() {
|
||||
// 获取当前体重
|
||||
this.currentWeight = weightManager.getCurrentWeight(this.petId) || 4.2
|
||||
|
||||
// 计算体重变化
|
||||
const weeklyChange = weightManager.calculateWeightChange(this.petId, 7)
|
||||
const monthlyChange = weightManager.calculateWeightChange(this.petId, 30)
|
||||
|
||||
// 更新周变化数据
|
||||
this.weeklyChange = weeklyChange.change >= 0 ? `+${weeklyChange.change}kg` : `${weeklyChange.change}kg`
|
||||
this.weeklyPercent = weeklyChange.percent >= 0 ? `+${weeklyChange.percent}%` : `${weeklyChange.percent}%`
|
||||
this.weeklyChangeClass = weeklyChange.trend
|
||||
|
||||
// 更新月变化数据
|
||||
this.monthlyChange = monthlyChange.change >= 0 ? `+${monthlyChange.change}kg` : `${monthlyChange.change}kg`
|
||||
this.monthlyPercent = monthlyChange.percent >= 0 ? `+${monthlyChange.percent}%` : `${monthlyChange.percent}%`
|
||||
this.monthlyChangeClass = monthlyChange.trend
|
||||
|
||||
// 判断健康状态
|
||||
const healthRange = weightManager.getHealthyWeightRange(this.petInfo)
|
||||
if (this.currentWeight >= healthRange.min && this.currentWeight <= healthRange.max) {
|
||||
this.weightStatusText = '健康范围'
|
||||
this.weightStatusClass = 'healthy'
|
||||
} else if (this.currentWeight < healthRange.min) {
|
||||
this.weightStatusText = '偏轻'
|
||||
this.weightStatusClass = 'warning'
|
||||
} else {
|
||||
this.weightStatusText = '偏重'
|
||||
this.weightStatusClass = 'warning'
|
||||
}
|
||||
|
||||
// 生成AI分析
|
||||
const aiAnalysis = weightManager.generateAIAnalysis(this.petId, this.petInfo)
|
||||
this.healthAssessment = aiAnalysis.healthAssessment
|
||||
this.trendAnalysis = aiAnalysis.trendAnalysis
|
||||
this.recommendations = aiAnalysis.recommendations
|
||||
this.medicalAdvice = aiAnalysis.medicalAdvice
|
||||
|
||||
// 更新最后更新时间
|
||||
const records = weightManager.getWeightRecords(this.petId)
|
||||
if (records.length > 0) {
|
||||
const lastRecord = records[records.length - 1]
|
||||
const lastDate = new Date(lastRecord.date)
|
||||
this.lastUpdateTime = `${lastDate.getMonth() + 1}-${lastDate.getDate()} ${lastDate.getHours()}:${lastDate.getMinutes().toString().padStart(2, '0')}`
|
||||
}
|
||||
},
|
||||
|
||||
switchTimeRange(timeKey) {
|
||||
this.activeTab = timeKey
|
||||
const timeMap = {
|
||||
'week': '近一周',
|
||||
'month': '近一月',
|
||||
'year': '近一年',
|
||||
'all': '全部历史'
|
||||
}
|
||||
this.currentTimeRange = timeMap[timeKey]
|
||||
this.generateChartData()
|
||||
},
|
||||
|
||||
generateChartData() {
|
||||
// 使用 weightManager 生成图表数据
|
||||
this.chartData = weightManager.generateChartData(this.petId, this.activeTab)
|
||||
},
|
||||
|
||||
addWeightRecord() {
|
||||
uni.showModal({
|
||||
title: '添加体重记录',
|
||||
editable: true,
|
||||
placeholderText: '请输入体重(kg)',
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
const weight = parseFloat(res.content)
|
||||
if (weight && weight > 0 && weight < 20) {
|
||||
const record = weightManager.addWeightRecord(this.petId, {
|
||||
weight: weight,
|
||||
date: new Date().toISOString(),
|
||||
note: '手动添加'
|
||||
})
|
||||
|
||||
if (record) {
|
||||
uni.showToast({
|
||||
title: '添加成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 重新加载数据
|
||||
this.loadWeightData()
|
||||
this.generateChartData()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '添加失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '请输入有效的体重值',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.weight-container {
|
||||
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 当前体重状态卡片 */
|
||||
.current-weight-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.weight-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.pet-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.last-update {
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.weight-display {
|
||||
text-align: center;
|
||||
|
||||
.current-weight {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.weight-value {
|
||||
font-size: 72rpx;
|
||||
font-weight: bold;
|
||||
color: #FF8A80;
|
||||
}
|
||||
|
||||
.weight-unit {
|
||||
font-size: 32rpx;
|
||||
color: #666666;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.weight-status {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
display: inline-block;
|
||||
|
||||
&.healthy {
|
||||
background: #E8F5E8;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background: #FFF3E0;
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background: #FFEBEE;
|
||||
color: #F44336;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 体重对比仪表盘 */
|
||||
.comparison-dashboard {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.dashboard-header {
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
.comparison-item {
|
||||
flex: 1;
|
||||
background: rgba(255, 138, 128, 0.05);
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.comparison-icon {
|
||||
font-size: 32rpx;
|
||||
width: 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comparison-content {
|
||||
flex: 1;
|
||||
|
||||
.comparison-label {
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.comparison-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
|
||||
&.increase {
|
||||
color: #FF8A80;
|
||||
}
|
||||
|
||||
&.decrease {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
&.stable {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.comparison-percent {
|
||||
font-size: 20rpx;
|
||||
display: block;
|
||||
|
||||
&.increase {
|
||||
color: #FF8A80;
|
||||
}
|
||||
|
||||
&.decrease {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
&.stable {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 时间维度切换 */
|
||||
.time-selector {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.selector-header {
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.selector-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.time-tabs {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
|
||||
.time-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16rpx 12rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(255, 138, 128, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
background: #FF8A80;
|
||||
|
||||
.tab-text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.chart-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AI分析卡片 */
|
||||
.ai-analysis-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.analysis-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.analysis-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.analysis-badge {
|
||||
background: linear-gradient(135deg, #64B5F6, #81C784);
|
||||
border-radius: 20rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
|
||||
.badge-text {
|
||||
font-size: 20rpx;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-content {
|
||||
.analysis-section {
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 12rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
display: block;
|
||||
|
||||
&.warning {
|
||||
color: #FF9800;
|
||||
background: #FFF3E0;
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加体重记录按钮 */
|
||||
.add-weight-btn {
|
||||
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
|
||||
border-radius: 28rpx;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.add-btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
/**
|
||||
* 宠物体重管理工具类
|
||||
* 负责体重数据的存储、分析和AI建议生成
|
||||
*/
|
||||
|
||||
class WeightManager {
|
||||
constructor() {
|
||||
this.storageKey = 'pet_weight_records'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宠物的体重记录
|
||||
* @param {string} petId 宠物ID
|
||||
* @returns {Array} 体重记录数组
|
||||
*/
|
||||
getWeightRecords(petId) {
|
||||
try {
|
||||
const allRecords = uni.getStorageSync(this.storageKey) || {}
|
||||
return allRecords[petId] || []
|
||||
} catch (error) {
|
||||
console.error('获取体重记录失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加体重记录
|
||||
* @param {string} petId 宠物ID
|
||||
* @param {Object} record 体重记录
|
||||
*/
|
||||
addWeightRecord(petId, record) {
|
||||
try {
|
||||
const allRecords = uni.getStorageSync(this.storageKey) || {}
|
||||
if (!allRecords[petId]) {
|
||||
allRecords[petId] = []
|
||||
}
|
||||
|
||||
const newRecord = {
|
||||
id: Date.now(),
|
||||
weight: record.weight,
|
||||
date: record.date || new Date().toISOString(),
|
||||
note: record.note || '',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
allRecords[petId].push(newRecord)
|
||||
allRecords[petId].sort((a, b) => new Date(a.date) - new Date(b.date))
|
||||
|
||||
uni.setStorageSync(this.storageKey, allRecords)
|
||||
return newRecord
|
||||
} catch (error) {
|
||||
console.error('添加体重记录失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前体重
|
||||
* @param {string} petId 宠物ID
|
||||
* @returns {number} 当前体重
|
||||
*/
|
||||
getCurrentWeight(petId) {
|
||||
const records = this.getWeightRecords(petId)
|
||||
if (records.length === 0) return 0
|
||||
return records[records.length - 1].weight
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算体重变化
|
||||
* @param {string} petId 宠物ID
|
||||
* @param {number} days 对比天数
|
||||
* @returns {Object} 变化数据
|
||||
*/
|
||||
calculateWeightChange(petId, days) {
|
||||
const records = this.getWeightRecords(petId)
|
||||
if (records.length < 2) {
|
||||
return {
|
||||
change: 0,
|
||||
percent: 0,
|
||||
trend: 'stable'
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const compareDate = new Date(now.getTime() - days * 24 * 60 * 60 * 1000)
|
||||
|
||||
const currentWeight = this.getCurrentWeight(petId)
|
||||
const compareRecord = records.find(record =>
|
||||
new Date(record.date) >= compareDate
|
||||
) || records[0]
|
||||
|
||||
const change = currentWeight - compareRecord.weight
|
||||
const percent = compareRecord.weight > 0 ? (change / compareRecord.weight) * 100 : 0
|
||||
|
||||
let trend = 'stable'
|
||||
if (Math.abs(percent) > 1) {
|
||||
trend = change > 0 ? 'increase' : 'decrease'
|
||||
}
|
||||
|
||||
return {
|
||||
change: Number(change.toFixed(1)),
|
||||
percent: Number(percent.toFixed(1)),
|
||||
trend: trend,
|
||||
previousWeight: compareRecord.weight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图表数据
|
||||
* @param {string} petId 宠物ID
|
||||
* @param {string} timeRange 时间范围
|
||||
* @returns {Object} 图表数据
|
||||
*/
|
||||
generateChartData(petId, timeRange) {
|
||||
const records = this.getWeightRecords(petId)
|
||||
if (records.length === 0) {
|
||||
return {
|
||||
categories: [],
|
||||
series: [{ name: "体重", data: [] }]
|
||||
}
|
||||
}
|
||||
|
||||
let filteredRecords = []
|
||||
const now = new Date()
|
||||
|
||||
switch (timeRange) {
|
||||
case 'week':
|
||||
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
filteredRecords = records.filter(record => new Date(record.date) >= weekAgo)
|
||||
break
|
||||
case 'month':
|
||||
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
filteredRecords = records.filter(record => new Date(record.date) >= monthAgo)
|
||||
break
|
||||
case 'year':
|
||||
const yearAgo = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000)
|
||||
filteredRecords = records.filter(record => new Date(record.date) >= yearAgo)
|
||||
break
|
||||
default:
|
||||
filteredRecords = records
|
||||
}
|
||||
|
||||
// 如果数据点太少,补充一些模拟数据
|
||||
if (filteredRecords.length === 0) {
|
||||
filteredRecords = this.generateMockData(timeRange)
|
||||
}
|
||||
|
||||
const categories = filteredRecords.map(record => {
|
||||
const date = new Date(record.date)
|
||||
if (timeRange === 'year' || timeRange === 'all') {
|
||||
return `${date.getMonth() + 1}月`
|
||||
} else {
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`
|
||||
}
|
||||
})
|
||||
|
||||
const data = filteredRecords.map(record => record.weight)
|
||||
|
||||
return {
|
||||
categories: categories,
|
||||
series: [{ name: "体重", data: data }]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模拟数据(用于演示)
|
||||
* @param {string} timeRange 时间范围
|
||||
* @returns {Array} 模拟数据
|
||||
*/
|
||||
generateMockData(timeRange) {
|
||||
const now = new Date()
|
||||
const mockData = []
|
||||
|
||||
switch (timeRange) {
|
||||
case 'week':
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000)
|
||||
mockData.push({
|
||||
weight: 4.0 + Math.random() * 0.4,
|
||||
date: date.toISOString()
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'month':
|
||||
for (let i = 4; i >= 0; i--) {
|
||||
const date = new Date(now.getTime() - i * 7 * 24 * 60 * 60 * 1000)
|
||||
mockData.push({
|
||||
weight: 3.9 + i * 0.075,
|
||||
date: date.toISOString()
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'year':
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
const date = new Date(now.getTime() - i * 60 * 24 * 60 * 60 * 1000)
|
||||
mockData.push({
|
||||
weight: 3.2 + (5 - i) * 0.2,
|
||||
date: date.toISOString()
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const date = new Date(now.getTime() - i * 90 * 24 * 60 * 60 * 1000)
|
||||
mockData.push({
|
||||
weight: 1.2 + (6 - i) * 0.5,
|
||||
date: date.toISOString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return mockData
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成AI健康分析
|
||||
* @param {string} petId 宠物ID
|
||||
* @param {Object} petInfo 宠物信息
|
||||
* @returns {Object} AI分析结果
|
||||
*/
|
||||
generateAIAnalysis(petId, petInfo) {
|
||||
const currentWeight = this.getCurrentWeight(petId)
|
||||
const weeklyChange = this.calculateWeightChange(petId, 7)
|
||||
const monthlyChange = this.calculateWeightChange(petId, 30)
|
||||
|
||||
// 根据品种、年龄、性别判断健康范围
|
||||
const healthRange = this.getHealthyWeightRange(petInfo)
|
||||
const isHealthy = currentWeight >= healthRange.min && currentWeight <= healthRange.max
|
||||
|
||||
// 健康评估
|
||||
let healthAssessment = ''
|
||||
if (isHealthy) {
|
||||
healthAssessment = `根据${petInfo.name}的品种(${petInfo.breed})、年龄(${petInfo.age}岁)和性别(${petInfo.gender}),当前体重${currentWeight}kg处于健康范围内(${healthRange.min}-${healthRange.max}kg)。`
|
||||
} else if (currentWeight < healthRange.min) {
|
||||
healthAssessment = `当前体重${currentWeight}kg低于健康范围(${healthRange.min}-${healthRange.max}kg),建议增加营养摄入。`
|
||||
} else {
|
||||
healthAssessment = `当前体重${currentWeight}kg超出健康范围(${healthRange.min}-${healthRange.max}kg),建议控制饮食并增加运动。`
|
||||
}
|
||||
|
||||
// 趋势分析
|
||||
let trendAnalysis = ''
|
||||
if (weeklyChange.trend === 'stable') {
|
||||
trendAnalysis = '近期体重保持稳定,这是一个良好的状态。'
|
||||
} else if (weeklyChange.trend === 'increase') {
|
||||
if (weeklyChange.percent > 5) {
|
||||
trendAnalysis = `近期体重上升较快(${weeklyChange.percent}%),需要关注饮食控制。`
|
||||
} else {
|
||||
trendAnalysis = `近期体重呈适度上升趋势(${weeklyChange.percent}%),增长速度适中。`
|
||||
}
|
||||
} else {
|
||||
if (weeklyChange.percent < -5) {
|
||||
trendAnalysis = `近期体重下降较快(${weeklyChange.percent}%),建议检查健康状况。`
|
||||
} else {
|
||||
trendAnalysis = `近期体重呈下降趋势(${weeklyChange.percent}%),请注意营养补充。`
|
||||
}
|
||||
}
|
||||
|
||||
// 建议
|
||||
let recommendations = this.generateRecommendations(currentWeight, healthRange, weeklyChange)
|
||||
|
||||
// 医疗建议
|
||||
let medicalAdvice = ''
|
||||
if (Math.abs(weeklyChange.percent) > 10 || !isHealthy) {
|
||||
medicalAdvice = '建议咨询兽医,进行专业的健康检查和营养指导。'
|
||||
}
|
||||
|
||||
return {
|
||||
healthAssessment,
|
||||
trendAnalysis,
|
||||
recommendations,
|
||||
medicalAdvice
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取健康体重范围
|
||||
* @param {Object} petInfo 宠物信息
|
||||
* @returns {Object} 健康体重范围
|
||||
*/
|
||||
getHealthyWeightRange(petInfo) {
|
||||
// 简化的品种体重范围映射
|
||||
const breedRanges = {
|
||||
'橘猫': { min: 3.5, max: 5.5 },
|
||||
'英短': { min: 3.0, max: 5.0 },
|
||||
'美短': { min: 3.5, max: 5.5 },
|
||||
'布偶': { min: 4.0, max: 7.0 },
|
||||
'波斯': { min: 3.0, max: 5.5 },
|
||||
'暹罗': { min: 2.5, max: 4.5 }
|
||||
}
|
||||
|
||||
const baseRange = breedRanges[petInfo.breed] || { min: 3.0, max: 6.0 }
|
||||
|
||||
// 根据性别调整(公猫通常比母猫重一些)
|
||||
if (petInfo.gender === '公') {
|
||||
baseRange.min += 0.5
|
||||
baseRange.max += 0.5
|
||||
}
|
||||
|
||||
return baseRange
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成个性化建议
|
||||
* @param {number} currentWeight 当前体重
|
||||
* @param {Object} healthRange 健康范围
|
||||
* @param {Object} weeklyChange 周变化
|
||||
* @returns {string} 建议内容
|
||||
*/
|
||||
generateRecommendations(currentWeight, healthRange, weeklyChange) {
|
||||
let recommendations = []
|
||||
|
||||
if (currentWeight < healthRange.min) {
|
||||
recommendations.push('增加高质量蛋白质摄入,如优质猫粮、煮熟的鸡胸肉')
|
||||
recommendations.push('少量多餐,每日3-4次定时喂食')
|
||||
recommendations.push('确保充足的饮水')
|
||||
} else if (currentWeight > healthRange.max) {
|
||||
recommendations.push('控制食物分量,减少高热量零食')
|
||||
recommendations.push('增加运动量,每日逗猫棒游戏20-30分钟')
|
||||
recommendations.push('选择低脂肪、高纤维的减肥猫粮')
|
||||
} else {
|
||||
recommendations.push('保持当前的饮食习惯,每日定时定量喂食')
|
||||
recommendations.push('适量运动,如逗猫棒游戏15-20分钟')
|
||||
recommendations.push('定期监测体重变化')
|
||||
}
|
||||
|
||||
if (weeklyChange.trend === 'increase' && weeklyChange.percent > 3) {
|
||||
recommendations.push('近期体重增长较快,建议减少零食摄入')
|
||||
} else if (weeklyChange.trend === 'decrease' && weeklyChange.percent < -3) {
|
||||
recommendations.push('近期体重下降,注意观察食欲和精神状态')
|
||||
}
|
||||
|
||||
return recommendations.join(';') + '。'
|
||||
}
|
||||
}
|
||||
|
||||
export default new WeightManager()
|
||||
Loading…
Reference in New Issue