feat(health): H5端新增用药统计页面
- 新增 medicationAdherence.js:统计API接口 - 新增 statistic/medication/index.vue:用药统计页面 - 概览卡片:总任务、已服药、漏服、服药率、准时率 - 趋势图:服药率和准时率折线图 - 时段分布:四时段统计卡片 - 日历视图:月度服药日历 - 修改 pages.json:添加页面路由 - 修改 statistic/index.vue:添加健康统计分析入口
This commit is contained in:
46
src/api/health/medicationAdherence.js
Normal file
46
src/api/health/medicationAdherence.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 获取总体依从性统计
|
||||||
|
export function getOverallAdherence(query) {
|
||||||
|
return request({
|
||||||
|
url: '/health/adherence/overall',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取每日依从性统计列表
|
||||||
|
export function getDailyAdherence(query) {
|
||||||
|
return request({
|
||||||
|
url: '/health/adherence/daily',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取日历视图数据
|
||||||
|
export function getCalendarData(personId, yearMonth) {
|
||||||
|
return request({
|
||||||
|
url: '/health/adherence/calendar',
|
||||||
|
method: 'get',
|
||||||
|
params: { personId, yearMonth }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取各时段服药统计
|
||||||
|
export function getTimeSlotAdherence(query) {
|
||||||
|
return request({
|
||||||
|
url: '/health/adherence/timeSlot',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取仪表盘概览数据
|
||||||
|
export function getDashboard(personId) {
|
||||||
|
return request({
|
||||||
|
url: '/health/adherence/dashboard',
|
||||||
|
method: 'get',
|
||||||
|
params: { personId }
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -238,6 +238,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
,
|
,
|
||||||
|
{
|
||||||
|
"path": "pages/statistic/medication/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用药统计",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,
|
||||||
{
|
{
|
||||||
"path": "pages/work/accounts/accountDealRecord/list",
|
"path": "pages/work/accounts/accountDealRecord/list",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@@ -74,6 +74,26 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 健康统计分析 -->
|
||||||
|
<view class="section-header" v-show="auth.hasPermi('health:medicationStatistic:query')">
|
||||||
|
<text class="section-title">健康统计分析</text>
|
||||||
|
</view>
|
||||||
|
<view class="grid-body" v-show="auth.hasPermi('health:medicationStatistic:query')">
|
||||||
|
<view class="grid-wrapper">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in healthGridList"
|
||||||
|
:key="index"
|
||||||
|
v-show="auth.hasPermi(item.permission)"
|
||||||
|
@click="navigateTo(item.path)"
|
||||||
|
class="grid-item">
|
||||||
|
<view class="item-icon" :style="{ background: item.color }">
|
||||||
|
<uni-icons :type="item.icon" size="22" color="#ffffff"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<text class="item-text">{{ item.text }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- <refresh></refresh> -->
|
<!-- <refresh></refresh> -->
|
||||||
</template>
|
</template>
|
||||||
@@ -109,6 +129,9 @@
|
|||||||
{ path: '/pages/statistic/bill/investAnalysis/index', text: '收益统计分析', icon: 'settings', color: 'linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%)', permission: 'invest:investAnalysis:list'},
|
{ path: '/pages/statistic/bill/investAnalysis/index', text: '收益统计分析', icon: 'settings', color: 'linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%)', permission: 'invest:investAnalysis:list'},
|
||||||
{ path: '/pages/statistic/bill/futuresAnalysis/index', text: '期货统计分析', icon: 'fire', color: 'linear-gradient(135deg, #fc5c65 0%, #fd79a8 100%)', permission: 'invest:futuresAnalysis:list'},
|
{ path: '/pages/statistic/bill/futuresAnalysis/index', text: '期货统计分析', icon: 'fire', color: 'linear-gradient(135deg, #fc5c65 0%, #fd79a8 100%)', permission: 'invest:futuresAnalysis:list'},
|
||||||
{ path: '/pages/statistic/bill/stocksAnalysis/index', text: '股票统计分析', icon: 'flag-filled', color: 'linear-gradient(135deg, #5f72bd 0%, #9b23ea 100%)', permission: 'invest:stocksAnalysis:list' }
|
{ path: '/pages/statistic/bill/stocksAnalysis/index', text: '股票统计分析', icon: 'flag-filled', color: 'linear-gradient(135deg, #5f72bd 0%, #9b23ea 100%)', permission: 'invest:stocksAnalysis:list' }
|
||||||
|
])
|
||||||
|
const healthGridList=ref([
|
||||||
|
{ path: '/pages/statistic/medication/index', text: '用药统计', icon: 'heart-filled', color: 'linear-gradient(135deg, #409EFF 0%, #67C23A 100%)', permission: 'health:medicationStatistic:query' }
|
||||||
])
|
])
|
||||||
function navigateTo(path) {
|
function navigateTo(path) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
|
|||||||
591
src/pages/statistic/medication/index.vue
Normal file
591
src/pages/statistic/medication/index.vue
Normal file
@@ -0,0 +1,591 @@
|
|||||||
|
<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