fix: 删除多余代码。
This commit is contained in:
@@ -1,591 +0,0 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar">
|
||||
<view class="navbar-content">
|
||||
<view class="navbar-left" @click="goBack">
|
||||
<uni-icons type="back" size="20" color="#ffffff"></uni-icons>
|
||||
</view>
|
||||
<view class="navbar-title">用药统计</view>
|
||||
<view class="navbar-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日期选择 -->
|
||||
<view class="date-selector">
|
||||
<view class="date-item" :class="{ active: dateType === 'week' }" @click="changeDateType('week')">近7天</view>
|
||||
<view class="date-item" :class="{ active: dateType === 'month' }" @click="changeDateType('month')">近30天</view>
|
||||
<view class="date-item" :class="{ active: dateType === 'quarter' }" @click="changeDateType('quarter')">近90天</view>
|
||||
</view>
|
||||
|
||||
<!-- 概览卡片 -->
|
||||
<view class="overview-section" v-if="overview">
|
||||
<view class="stat-row">
|
||||
<view class="stat-item">
|
||||
<view class="stat-value">{{ overview.totalTasks || 0 }}</view>
|
||||
<view class="stat-label">总任务</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-value success">{{ overview.completedTasks || 0 }}</view>
|
||||
<view class="stat-label">已服药</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-value danger">{{ overview.missedTasks || 0 }}</view>
|
||||
<view class="stat-label">漏服</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-row">
|
||||
<view class="stat-item large">
|
||||
<view class="progress-ring">
|
||||
<view class="ring-value">{{ overview.adherenceRate || 0 }}%</view>
|
||||
<view class="ring-label">服药率</view>
|
||||
</view>
|
||||
<u-line-progress :percentage="overview.adherenceRate || 0" activeColor="#67C23A" height="8"></u-line-progress>
|
||||
</view>
|
||||
<view class="stat-item large">
|
||||
<view class="progress-ring">
|
||||
<view class="ring-value">{{ overview.ontimeRate || 0 }}%</view>
|
||||
<view class="ring-label">准时率</view>
|
||||
</view>
|
||||
<u-line-progress :percentage="overview.ontimeRate || 0" activeColor="#409EFF" height="8"></u-line-progress>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 趋势图 -->
|
||||
<view class="chart-section">
|
||||
<view class="section-title">服药趋势</view>
|
||||
<view class="chart-container" v-if="dailyList.length > 0">
|
||||
<qiun-data-charts type="line" :chartData="trendChartData" :opts="trendOpts" />
|
||||
</view>
|
||||
<u-empty v-else text="暂无数据" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 时段分布 -->
|
||||
<view class="chart-section">
|
||||
<view class="section-title">时段分布</view>
|
||||
<view class="time-slot-grid" v-if="timeSlotData">
|
||||
<view class="time-slot-item" v-for="(item, key) in timeSlotData" :key="key">
|
||||
<view class="slot-name">{{ timeSlotNames[key] }}</view>
|
||||
<view class="slot-rate">{{ item.adherenceRate || 0 }}%</view>
|
||||
<view class="slot-detail">
|
||||
<text>已服 {{ item.completedTasks || 0 }}</text>
|
||||
<text>/ {{ item.totalTasks || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日历视图 -->
|
||||
<view class="calendar-section">
|
||||
<view class="section-header">
|
||||
<view class="section-title">日历视图</view>
|
||||
<view class="month-nav">
|
||||
<uni-icons type="left" size="18" @click="changeMonth(-1)"></uni-icons>
|
||||
<text class="current-month">{{ currentMonthText }}</text>
|
||||
<uni-icons type="right" size="18" @click="changeMonth(1)"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="calendar-grid">
|
||||
<view class="calendar-header">
|
||||
<text v-for="day in ['一', '二', '三', '四', '五', '六', '日']" :key="day" class="header-cell">{{ day }}</text>
|
||||
</view>
|
||||
<view class="calendar-body">
|
||||
<view
|
||||
v-for="(day, index) in calendarDays"
|
||||
:key="index"
|
||||
class="calendar-cell"
|
||||
:class="{
|
||||
'other-month': day.otherMonth,
|
||||
'today': day.isToday,
|
||||
'has-data': day.data && day.data.totalTasks > 0
|
||||
}"
|
||||
>
|
||||
<text class="day-number">{{ day.day }}</text>
|
||||
<view class="day-indicator" v-if="day.data && day.data.totalTasks > 0">
|
||||
<view class="indicator-dot" :class="getIndicatorClass(day.data)"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="calendar-legend">
|
||||
<view class="legend-item">
|
||||
<view class="legend-dot good"></view>
|
||||
<text>服药率 ≥ 80%</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="legend-dot normal"></view>
|
||||
<text>服药率 50-80%</text>
|
||||
</view>
|
||||
<view class="legend-item">
|
||||
<view class="legend-dot bad"></view>
|
||||
<text>服药率 < 50%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getOverallAdherence, getDailyAdherence, getCalendarData, getTimeSlotAdherence } from '@/api/health/medicationAdherence'
|
||||
|
||||
const dateType = ref('week')
|
||||
const overview = ref(null)
|
||||
const dailyList = ref([])
|
||||
const timeSlotData = ref(null)
|
||||
const calendarData = ref([])
|
||||
|
||||
// 当前月份
|
||||
const currentYearMonth = ref('')
|
||||
const currentMonthText = ref('')
|
||||
|
||||
// 时段名称映射
|
||||
const timeSlotNames = {
|
||||
morning: '上午(6-12)',
|
||||
afternoon: '下午(12-18)',
|
||||
evening: '晚间(18-24)',
|
||||
night: '凌晨(0-6)'
|
||||
}
|
||||
|
||||
// 趋势图配置
|
||||
const trendOpts = ref({
|
||||
color: ['#409EFF', '#67C23A'],
|
||||
padding: [15, 10, 0, 15],
|
||||
legend: {},
|
||||
xAxis: {
|
||||
disableGrid: true
|
||||
},
|
||||
yAxis: {
|
||||
data: [{ min: 0, max: 100 }]
|
||||
},
|
||||
extra: {
|
||||
line: {
|
||||
type: 'curve',
|
||||
width: 2,
|
||||
activeType: 'hollow'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 趋势图数据
|
||||
const trendChartData = computed(() => {
|
||||
const categories = dailyList.value.map(d => d.date?.substring(5) || '')
|
||||
const series = [
|
||||
{
|
||||
name: '服药率',
|
||||
data: dailyList.value.map(d => d.adherenceRate || 0)
|
||||
},
|
||||
{
|
||||
name: '准时率',
|
||||
data: dailyList.value.map(d => d.ontimeRate || 0)
|
||||
}
|
||||
]
|
||||
return { categories, series }
|
||||
})
|
||||
|
||||
// 日历天数计算
|
||||
const calendarDays = computed(() => {
|
||||
const days = []
|
||||
if (!currentYearMonth.value) return days
|
||||
|
||||
const [year, month] = currentYearMonth.value.split('-').map(Number)
|
||||
const firstDay = new Date(year, month - 1, 1)
|
||||
const lastDay = new Date(year, month, 0)
|
||||
const startWeekday = (firstDay.getDay() + 6) % 7
|
||||
|
||||
// 上月填充
|
||||
const prevMonthLastDay = new Date(year, month - 1, 0).getDate()
|
||||
for (let i = startWeekday - 1; i >= 0; i--) {
|
||||
days.push({ day: prevMonthLastDay - i, otherMonth: true, isToday: false, data: null })
|
||||
}
|
||||
|
||||
// 当月
|
||||
const today = new Date()
|
||||
const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
|
||||
|
||||
const dataMap = {}
|
||||
calendarData.value.forEach(d => { dataMap[d.date] = d })
|
||||
|
||||
for (let i = 1; i <= lastDay.getDate(); i++) {
|
||||
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`
|
||||
days.push({
|
||||
day: i,
|
||||
otherMonth: false,
|
||||
isToday: dateStr === todayStr,
|
||||
data: dataMap[dateStr] || null
|
||||
})
|
||||
}
|
||||
|
||||
// 下月填充
|
||||
const remaining = 42 - days.length
|
||||
for (let i = 1; i <= remaining; i++) {
|
||||
days.push({ day: i, otherMonth: true, isToday: false, data: null })
|
||||
}
|
||||
|
||||
return days
|
||||
})
|
||||
|
||||
// 获取日期范围
|
||||
const getDateRange = () => {
|
||||
const today = new Date()
|
||||
let days = 7
|
||||
if (dateType.value === 'month') days = 30
|
||||
if (dateType.value === 'quarter') days = 90
|
||||
|
||||
const endDate = today.toISOString().split('T')[0]
|
||||
const startDate = new Date(today.getTime() - days * 24 * 60 * 60 * 1000).toISOString().split('T')[0]
|
||||
return { startDate, endDate }
|
||||
}
|
||||
|
||||
// 加载统计数据
|
||||
const loadStatistics = async () => {
|
||||
const { startDate, endDate } = getDateRange()
|
||||
const params = { startDate, endDate }
|
||||
|
||||
try {
|
||||
// 总体统计
|
||||
const overviewRes = await getOverallAdherence(params)
|
||||
overview.value = overviewRes.data
|
||||
|
||||
// 每日趋势
|
||||
const dailyRes = await getDailyAdherence(params)
|
||||
dailyList.value = dailyRes.data || []
|
||||
|
||||
// 时段统计
|
||||
const timeSlotRes = await getTimeSlotAdherence(params)
|
||||
timeSlotData.value = timeSlotRes.data
|
||||
} catch (e) {
|
||||
console.error('加载统计数据失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载日历数据
|
||||
const loadCalendarData = async () => {
|
||||
try {
|
||||
const res = await getCalendarData(null, currentYearMonth.value)
|
||||
calendarData.value = res.data || []
|
||||
} catch (e) {
|
||||
console.error('加载日历数据失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换日期类型
|
||||
const changeDateType = (type) => {
|
||||
dateType.value = type
|
||||
loadStatistics()
|
||||
}
|
||||
|
||||
// 切换月份
|
||||
const changeMonth = (delta) => {
|
||||
const [year, month] = currentYearMonth.value.split('-').map(Number)
|
||||
const newDate = new Date(year, month - 1 + delta, 1)
|
||||
currentYearMonth.value = `${newDate.getFullYear()}-${String(newDate.getMonth() + 1).padStart(2, '0')}`
|
||||
currentMonthText.value = `${newDate.getFullYear()}年${newDate.getMonth() + 1}月`
|
||||
loadCalendarData()
|
||||
}
|
||||
|
||||
// 获取日历单元格指示器样式
|
||||
const getIndicatorClass = (data) => {
|
||||
const rate = data.adherenceRate || 0
|
||||
if (rate >= 80) return 'good'
|
||||
if (rate >= 50) return 'normal'
|
||||
return 'bad'
|
||||
}
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化月份
|
||||
const today = new Date()
|
||||
currentYearMonth.value = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`
|
||||
currentMonthText.value = `${today.getFullYear()}年${today.getMonth() + 1}月`
|
||||
|
||||
loadStatistics()
|
||||
loadCalendarData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fa;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.custom-navbar {
|
||||
background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
|
||||
padding-top: 88rpx;
|
||||
padding-bottom: 30rpx;
|
||||
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
color: #ffffff;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar-left, .navbar-right {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.date-selector {
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
padding: 20rpx 30rpx;
|
||||
gap: 20rpx;
|
||||
|
||||
.date-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16rpx 0;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
background: #f5f7fa;
|
||||
|
||||
&.active {
|
||||
background: #409EFF;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
background: #ffffff;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.stat-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
|
||||
&.large {
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
|
||||
&.success { color: #67C23A; }
|
||||
&.danger { color: #F56C6C; }
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.progress-ring {
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.ring-value {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.ring-label {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
background: #ffffff;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 400rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time-slot-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
|
||||
.time-slot-item {
|
||||
background: #f5f7fa;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
|
||||
.slot-name {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.slot-rate {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.slot-detail {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-section {
|
||||
background: #ffffff;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.month-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
|
||||
.current-month {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
.calendar-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #EBEEF5;
|
||||
|
||||
.header-cell {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 4rpx;
|
||||
padding-top: 10rpx;
|
||||
|
||||
.calendar-cell {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&.other-month {
|
||||
.day-number { color: #C0C4CC; }
|
||||
}
|
||||
|
||||
&.today {
|
||||
background: #ECF5FF;
|
||||
.day-number { color: #409EFF; font-weight: bold; }
|
||||
}
|
||||
|
||||
&.has-data {
|
||||
.day-indicator {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.day-number {
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.indicator-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&.good { background: #67C23A; }
|
||||
&.normal { background: #E6A23C; }
|
||||
&.bad { background: #F56C6C; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30rpx;
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #EBEEF5;
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
|
||||
.legend-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&.good { background: #67C23A; }
|
||||
&.normal { background: #E6A23C; }
|
||||
&.bad { background: #F56C6C; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user