726 lines
16 KiB
Vue
726 lines
16 KiB
Vue
<template>
|
||
<view class="weight-container page-container-with-bg">
|
||
<!-- 当前体重状态卡片 -->
|
||
<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>
|
||
// 体重管理器 - 从 utils/weightManager.js 移入
|
||
const weightManager = {
|
||
storageKey: 'pet_weight_records',
|
||
|
||
getWeightRecords(petId) {
|
||
try {
|
||
const allRecords = uni.getStorageSync(this.storageKey) || {}
|
||
return allRecords[petId] || []
|
||
} catch (error) {
|
||
console.error('获取体重记录失败:', error)
|
||
return []
|
||
}
|
||
},
|
||
|
||
addWeightRecord(petId, weight, date, note = '') {
|
||
try {
|
||
const allRecords = uni.getStorageSync(this.storageKey) || {}
|
||
if (!allRecords[petId]) {
|
||
allRecords[petId] = []
|
||
}
|
||
|
||
const newRecord = {
|
||
id: Date.now(),
|
||
petId: petId,
|
||
weight: parseFloat(weight),
|
||
date: date,
|
||
note: note,
|
||
createTime: new Date().toISOString()
|
||
}
|
||
|
||
allRecords[petId].push(newRecord)
|
||
allRecords[petId].sort((a, b) => new Date(a.date) - new Date(b.date))
|
||
|
||
uni.setStorageSync(this.storageKey, allRecords)
|
||
return true
|
||
} catch (error) {
|
||
console.error('添加体重记录失败:', error)
|
||
return false
|
||
}
|
||
},
|
||
|
||
deleteWeightRecord(petId, recordId) {
|
||
try {
|
||
const allRecords = uni.getStorageSync(this.storageKey) || {}
|
||
if (allRecords[petId]) {
|
||
allRecords[petId] = allRecords[petId].filter(record => record.id !== recordId)
|
||
uni.setStorageSync(this.storageKey, allRecords)
|
||
}
|
||
return true
|
||
} catch (error) {
|
||
console.error('删除体重记录失败:', error)
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
|
||
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.initializeTestDataIfNeeded()
|
||
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)
|
||
}
|
||
},
|
||
|
||
initializeTestDataIfNeeded() {
|
||
// 检查是否已有体重数据,如果没有则初始化测试数据
|
||
const records = weightManager.getWeightRecords(this.petId)
|
||
console.log(`宠物 ${this.petId} 的体重记录数量:`, records.length)
|
||
},
|
||
|
||
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>
|