pet/pages/pets/pet-vaccine.vue

990 lines
22 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="vaccine-container page-container-with-bg">
<!-- 疫苗状态概览卡片 -->
<view class="status-overview-card">
<view class="overview-header">
<text class="pet-name">{{ petName }}的疫苗管理</text>
<text class="last-update">最后更新{{ lastUpdateTime }}</text>
</view>
<view class="status-display">
<view class="completion-circle">
<view class="circle-progress" :style="{ background: `conic-gradient(#4CAF50 0deg, #4CAF50 ${completionRate * 3.6}deg, #E0E0E0 ${completionRate * 3.6}deg)` }">
<view class="circle-center">
<text class="completion-rate">{{ completionRate }}%</text>
<text class="completion-label">完成度</text>
</view>
</view>
</view>
<view class="status-stats">
<view class="stat-item">
<text class="stat-number" style="color: #4CAF50;">{{ statusSummary.completed }}</text>
<text class="stat-label">已完成</text>
</view>
<view class="stat-item">
<text class="stat-number" style="color: #FF9800;">{{ statusSummary.expiringSoon }}</text>
<text class="stat-label">即将到期</text>
</view>
<view class="stat-item">
<text class="stat-number" style="color: #F44336;">{{ statusSummary.expired }}</text>
<text class="stat-label">已过期</text>
</view>
<view class="stat-item">
<text class="stat-number" style="color: #9E9E9E;">{{ statusSummary.missing }}</text>
<text class="stat-label">未接种</text>
</view>
</view>
</view>
</view>
<!-- 疫苗分类切换 -->
<view class="category-selector">
<view class="selector-header">
<text class="selector-title">💉 疫苗分类</text>
</view>
<view class="category-tabs">
<view
class="category-tab"
:class="{ active: activeCategory === 'all' }"
@click="switchCategory('all')"
>
<text class="tab-text">全部</text>
</view>
<view
class="category-tab"
:class="{ active: activeCategory === 'core' }"
@click="switchCategory('core')"
>
<text class="tab-text">核心疫苗</text>
</view>
<view
class="category-tab"
:class="{ active: activeCategory === 'nonCore' }"
@click="switchCategory('nonCore')"
>
<text class="tab-text">非核心疫苗</text>
</view>
<view
class="category-tab"
:class="{ active: activeCategory === 'plan' }"
@click="switchCategory('plan')"
>
<text class="tab-text">接种计划</text>
</view>
</view>
</view>
<!-- 疫苗记录列表 -->
<view class="vaccine-records" v-if="activeCategory !== 'plan'">
<view class="records-header">
<text class="records-title">📋 疫苗记录</text>
<view class="add-record-btn" @click="addVaccineRecord">
<text class="add-btn-text">+ 添加</text>
</view>
</view>
<view class="records-timeline" v-if="filteredRecords.length > 0">
<view
class="timeline-item"
v-for="record in filteredRecords"
:key="record.id"
>
<view class="timeline-dot" :class="getStatusClass(record)"></view>
<view class="timeline-content">
<view class="vaccine-card" :class="getStatusClass(record)">
<view class="vaccine-header">
<text class="vaccine-name">{{ record.vaccineName }}</text>
<view class="vaccine-status" :class="getStatusClass(record)">
<text class="status-text">{{ getStatusText(record) }}</text>
</view>
</view>
<view class="vaccine-details">
<view class="detail-row">
<text class="detail-label">接种日期:</text>
<text class="detail-value">{{ formatDate(record.vaccineDate) }}</text>
</view>
<view class="detail-row">
<text class="detail-label">有效期至:</text>
<text class="detail-value">{{ formatDate(record.expiryDate) }}</text>
</view>
<view class="detail-row" v-if="record.hospital">
<text class="detail-label">接种医院:</text>
<text class="detail-value">{{ record.hospital }}</text>
</view>
<view class="detail-row" v-if="record.batchNumber">
<text class="detail-label">批次号:</text>
<text class="detail-value">{{ record.batchNumber }}</text>
</view>
<view class="detail-row" v-if="record.note">
<text class="detail-label">备注:</text>
<text class="detail-value">{{ record.note }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="empty-records" v-else>
<text class="empty-icon">💉</text>
<text class="empty-title">暂无{{ getCategoryName() }}记录</text>
<text class="empty-subtitle">点击添加按钮开始记录疫苗接种信息</text>
</view>
</view>
<!-- 接种计划 -->
<view class="vaccine-plan" v-if="activeCategory === 'plan'">
<view class="plan-header">
<text class="plan-title">📅 接种计划</text>
</view>
<view class="plan-list" v-if="vaccinePlan.length > 0">
<view
class="plan-item"
v-for="plan in vaccinePlan"
:key="plan.vaccineType"
:class="getPriorityClass(plan.priority)"
>
<view class="plan-priority" :class="getPriorityClass(plan.priority)">
<text class="priority-text">{{ getPriorityText(plan.priority) }}</text>
</view>
<view class="plan-content">
<text class="plan-vaccine-name">{{ plan.vaccineName }}</text>
<text class="plan-due-date">建议接种时间:{{ formatDate(plan.nextDueDate) }}</text>
<text class="plan-reason">{{ plan.reason }}</text>
</view>
<view class="plan-action" @click="scheduleVaccine(plan)">
<text class="action-text">安排接种</text>
</view>
</view>
</view>
</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">{{ aiAnalysis.completenessAnalysis }}</text>
</view>
<view class="analysis-section">
<text class="section-title">⏰ 时效性分析</text>
<text class="section-content">{{ aiAnalysis.timelinessAnalysis }}</text>
</view>
<view class="analysis-section">
<text class="section-title">💡 个性化建议</text>
<text class="section-content">{{ aiAnalysis.recommendations }}</text>
</view>
<view class="analysis-section" v-if="aiAnalysis.riskAssessment">
<text class="section-title">⚠️ 风险提醒</text>
<text class="section-content warning">{{ aiAnalysis.riskAssessment }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
// 疫苗管理器 - 从 utils/vaccineManager.js 移入
const vaccineManager = {
storageKey: 'pet_vaccine_records',
getVaccineRecords(petId) {
try {
const allRecords = uni.getStorageSync(this.storageKey) || {}
return allRecords[petId] || []
} catch (error) {
console.error('获取疫苗记录失败:', error)
return []
}
},
addVaccineRecord(petId, vaccineData) {
try {
const allRecords = uni.getStorageSync(this.storageKey) || {}
if (!allRecords[petId]) {
allRecords[petId] = []
}
const newRecord = {
id: Date.now(),
petId: petId,
...vaccineData,
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
}
},
deleteVaccineRecord(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
}
},
getVaccineTypes() {
return [
{ value: 'rabies', label: '狂犬疫苗', required: true },
{ value: 'dhpp', label: '四联疫苗', required: true },
{ value: 'feline', label: '猫三联', required: true },
{ value: 'other', label: '其他疫苗', required: false }
]
}
}
export default {
data() {
return {
petId: '',
petName: '',
petInfo: {},
lastUpdateTime: '',
// 状态统计
statusSummary: {
completed: 0,
expiringSoon: 0,
expired: 0,
missing: 0,
total: 0,
completionRate: 0
},
completionRate: 0,
// 分类切换
activeCategory: 'all',
// 疫苗记录
allRecords: [],
filteredRecords: [],
// 接种计划
vaccinePlan: [],
// AI分析
aiAnalysis: {
completenessAnalysis: '',
timelinessAnalysis: '',
recommendations: '',
riskAssessment: ''
}
}
},
onLoad(options) {
this.petId = options.petId || ''
this.petName = options.petName || '宠物'
this.loadPetInfo()
this.loadVaccineData()
},
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)
}
},
loadVaccineData() {
// 获取疫苗记录
this.allRecords = vaccineManager.getVaccineRecords(this.petId)
this.filterRecords()
// 获取状态统计
this.statusSummary = vaccineManager.getVaccineStatusSummary(this.petId)
this.completionRate = this.statusSummary.completionRate
// 生成接种计划
this.vaccinePlan = vaccineManager.generateVaccinePlan(this.petId, this.petInfo)
// 生成AI分析
this.aiAnalysis = vaccineManager.generateAIAnalysis(this.petId, this.petInfo)
// 更新最后更新时间
if (this.allRecords.length > 0) {
const latestRecord = this.allRecords.sort((a, b) => new Date(b.vaccineDate) - new Date(a.vaccineDate))[0]
const lastDate = new Date(latestRecord.vaccineDate)
this.lastUpdateTime = `${lastDate.getMonth() + 1}-${lastDate.getDate()} ${lastDate.getHours()}:${lastDate.getMinutes().toString().padStart(2, '0')}`
} else {
this.lastUpdateTime = '暂无记录'
}
},
switchCategory(category) {
this.activeCategory = category
this.filterRecords()
},
filterRecords() {
if (this.activeCategory === 'all') {
this.filteredRecords = this.allRecords
} else {
this.filteredRecords = this.allRecords.filter(record => record.category === this.activeCategory)
}
// 按接种日期倒序排列
this.filteredRecords.sort((a, b) => new Date(b.vaccineDate) - new Date(a.vaccineDate))
},
getCategoryName() {
const categoryNames = {
'all': '疫苗',
'core': '核心疫苗',
'nonCore': '非核心疫苗'
}
return categoryNames[this.activeCategory] || '疫苗'
},
getStatusClass(record) {
const status = vaccineManager.getVaccineStatus(record)
return status // 'valid', 'expiring', 'expired'
},
getStatusText(record) {
const status = vaccineManager.getVaccineStatus(record)
const statusTexts = {
'valid': '有效',
'expiring': '即将到期',
'expired': '已过期'
}
return statusTexts[status] || '未知'
},
getPriorityClass(priority) {
return priority // 'high', 'medium', 'low'
},
getPriorityText(priority) {
const priorityTexts = {
'high': '紧急',
'medium': '建议',
'low': '可选'
}
return priorityTexts[priority] || '未知'
},
formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
},
addVaccineRecord() {
uni.showModal({
title: '添加疫苗记录',
content: '跳转到添加疫苗记录页面?',
success: (res) => {
if (res.confirm) {
// 这里可以跳转到添加疫苗记录的页面
// 或者显示一个表单弹窗
this.showAddVaccineForm()
}
}
})
},
showAddVaccineForm() {
// 简化版添加功能,实际项目中可以创建专门的表单页面
uni.showToast({
title: '功能开发中',
icon: 'none'
})
},
scheduleVaccine(plan) {
uni.showModal({
title: '安排接种',
content: `是否要安排${plan.vaccineName}的接种?`,
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '已添加到日程',
icon: 'success'
})
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.vaccine-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding: 20rpx;
}
/* 疫苗状态概览卡片 */
.status-overview-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);
.overview-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;
}
}
.status-display {
display: flex;
align-items: center;
gap: 32rpx;
.completion-circle {
.circle-progress {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.circle-center {
width: 80rpx;
height: 80rpx;
background: white;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.completion-rate {
font-size: 24rpx;
font-weight: bold;
color: #4CAF50;
line-height: 1;
}
.completion-label {
font-size: 18rpx;
color: #999999;
margin-top: 4rpx;
}
}
}
}
.status-stats {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
.stat-item {
text-align: center;
.stat-number {
font-size: 28rpx;
font-weight: bold;
display: block;
margin-bottom: 6rpx;
}
.stat-label {
font-size: 20rpx;
color: #999999;
display: block;
}
}
}
}
}
/* 分类选择器 */
.category-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;
}
}
.category-tabs {
display: flex;
gap: 12rpx;
.category-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;
}
}
}
}
/* 疫苗记录列表 */
.vaccine-records {
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);
.records-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28rpx;
.records-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.add-record-btn {
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
border-radius: 20rpx;
padding: 12rpx 24rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
}
.add-btn-text {
font-size: 24rpx;
color: white;
font-weight: 500;
}
}
}
.records-timeline {
position: relative;
&::before {
content: '';
position: absolute;
left: 24rpx;
top: 0;
bottom: 0;
width: 2rpx;
background: #E0E0E0;
}
.timeline-item {
position: relative;
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.timeline-dot {
position: absolute;
left: 16rpx;
top: 24rpx;
width: 16rpx;
height: 16rpx;
border-radius: 50%;
z-index: 1;
&.valid {
background: #4CAF50;
}
&.expiring {
background: #FF9800;
}
&.expired {
background: #F44336;
}
}
.timeline-content {
margin-left: 56rpx;
.vaccine-card {
background: white;
border-radius: 20rpx;
padding: 24rpx;
border-left: 4rpx solid #E0E0E0;
transition: all 0.3s ease;
&.valid {
border-left-color: #4CAF50;
}
&.expiring {
border-left-color: #FF9800;
}
&.expired {
border-left-color: #F44336;
}
.vaccine-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.vaccine-name {
font-size: 28rpx;
font-weight: bold;
color: #333333;
}
.vaccine-status {
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-size: 20rpx;
&.valid {
background: #E8F5E8;
color: #4CAF50;
}
&.expiring {
background: #FFF3E0;
color: #FF9800;
}
&.expired {
background: #FFEBEE;
color: #F44336;
}
.status-text {
font-weight: 500;
}
}
}
.vaccine-details {
.detail-row {
display: flex;
margin-bottom: 8rpx;
&:last-child {
margin-bottom: 0;
}
.detail-label {
font-size: 22rpx;
color: #999999;
min-width: 120rpx;
}
.detail-value {
font-size: 22rpx;
color: #666666;
flex: 1;
}
}
}
}
}
}
}
.empty-records {
text-align: center;
padding: 80rpx 40rpx;
.empty-icon {
font-size: 80rpx;
margin-bottom: 24rpx;
display: block;
}
.empty-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 12rpx;
display: block;
}
.empty-subtitle {
font-size: 26rpx;
color: #999999;
display: block;
}
}
}
/* 接种计划 */
.vaccine-plan {
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);
.plan-header {
margin-bottom: 28rpx;
.plan-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
}
.plan-list {
.plan-item {
display: flex;
align-items: center;
gap: 16rpx;
background: white;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 16rpx;
border-left: 4rpx solid #E0E0E0;
&:last-child {
margin-bottom: 0;
}
&.high {
border-left-color: #F44336;
}
&.medium {
border-left-color: #FF9800;
}
&.low {
border-left-color: #4CAF50;
}
.plan-priority {
padding: 6rpx 12rpx;
border-radius: 12rpx;
font-size: 20rpx;
font-weight: 500;
&.high {
background: #FFEBEE;
color: #F44336;
}
&.medium {
background: #FFF3E0;
color: #FF9800;
}
&.low {
background: #E8F5E8;
color: #4CAF50;
}
.priority-text {
font-size: 20rpx;
}
}
.plan-content {
flex: 1;
.plan-vaccine-name {
font-size: 26rpx;
font-weight: bold;
color: #333333;
display: block;
margin-bottom: 6rpx;
}
.plan-due-date {
font-size: 22rpx;
color: #666666;
display: block;
margin-bottom: 4rpx;
}
.plan-reason {
font-size: 20rpx;
color: #999999;
display: block;
}
}
.plan-action {
background: rgba(255, 138, 128, 0.1);
border-radius: 16rpx;
padding: 12rpx 20rpx;
transition: all 0.3s ease;
&:active {
background: rgba(255, 138, 128, 0.2);
transform: scale(0.95);
}
.action-text {
font-size: 22rpx;
color: #FF8A80;
font-weight: 500;
}
}
}
}
}
/* AI分析卡片 */
.ai-analysis-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-radius: 28rpx;
padding: 32rpx;
margin-bottom: 40rpx;
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;
}
}
}
}
}
</style>