This commit is contained in:
parent
af722a2bbc
commit
34a3f7bf1f
|
|
@ -64,6 +64,14 @@
|
||||||
"navigationBarTextStyle": "white"
|
"navigationBarTextStyle": "white"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/pets/pet-weight",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "体重管理",
|
||||||
|
"navigationBarBackgroundColor": "#FF8A80",
|
||||||
|
"navigationBarTextStyle": "white"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": "pages/pets/pet-chat-simple",
|
"path": "pages/pets/pet-chat-simple",
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,10 @@
|
||||||
<text class="stat-number">{{ petInfo.companionDays || 0 }}</text>
|
<text class="stat-number">{{ petInfo.companionDays || 0 }}</text>
|
||||||
<text class="stat-label">陪伴天数</text>
|
<text class="stat-label">陪伴天数</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item">
|
<view class="stat-item clickable" @click="goToWeightManagement">
|
||||||
<text class="stat-number">{{ petInfo.weight || '4.2kg' }}</text>
|
<text class="stat-number">{{ petInfo.weight || '4.2kg' }}</text>
|
||||||
<text class="stat-label">当前体重</text>
|
<text class="stat-label">当前体重</text>
|
||||||
|
<text class="stat-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<text class="stat-number">85</text>
|
<text class="stat-number">85</text>
|
||||||
|
|
@ -359,6 +360,12 @@ export default {
|
||||||
this.viewRecords()
|
this.viewRecords()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
goToWeightManagement() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/pets/pet-weight?petId=${this.petId}&petName=${this.petInfo.name}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
shareProfile() {
|
shareProfile() {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '分享功能开发中',
|
title: '分享功能开发中',
|
||||||
|
|
@ -555,6 +562,24 @@ export default {
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
text-align: center;
|
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 {
|
.stat-number {
|
||||||
font-size: 32rpx;
|
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