Files
intc-vue-h5/src/pages/health/statistic/healthStatistic/index.vue
2026-02-05 20:43:34 +08:00

1272 lines
34 KiB
Vue
Raw 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="container">
<u-sticky offsetTop="0rpx" customNavHeight="0rpx">
<view class="search-view">
<u-input v-model="queryParams.personName" border="false" type="select" @click="handlePerson" placeholder="请选择人员" suffixIcon="search"
suffixIconStyle="color: #909399" class="search-input">
</u-input>
</view>
<view class="search-view">
<u-input v-model="queryParams.healthRecordName" border="false" type="select" @click="handleHealthRecord" placeholder="请选择健康档案" suffixIcon="search"
suffixIconStyle="color: #909399" class="search-input">
</u-input>
</view>
<view class="search-view">
<u-input v-model="queryParams.time" placeholder="请选择建档日期" border="false" type="select" readonly suffixIcon="calendar"
suffixIconStyle="color: #909399" class="search-input">
</u-input>
<view class="filter-btn" @click="filterPanel = !filterPanel">
<uni-icons type="list" size="18" color="#667eea"></uni-icons>
<text>筛选</text>
</view>
</view>
<view class="app-container">
<view class="header-con" ref="searchHeightRef">
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #5b51d8 0%, #6b21a8 100%);">
<uni-icons type="folder-add-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">档案数量</view>
<view class="num">{{ record.healthRecordCount }}<span></span></view>
</view>
</view>
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #c026d3 0%, #dc2626 100%);">
<uni-icons type="calendar-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">平均康复周期</view>
<view class="num">
{{ record.perDuration ? record.perDuration.split(',')[0] : '0' }}
<span></span>
{{ record.perDuration ? record.perDuration.split(',')[1] : '0' }}
<span>小时</span>
</view>
</view>
</view>
</view>
<view class="header-con" ref="searchHeightRef">
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #0284c7 0%, #0891b2 100%);">
<uni-icons type="home-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">就医医院</view>
<view class="num">{{ record.hospitalCount }}<span></span></view>
</view>
</view>
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #059669 0%, #0d9488 100%);">
<uni-icons type="person-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">就诊大夫</view>
<view class="num">{{ record.doctorTotalCount }}<span></span></view>
</view>
</view>
</view>
<view class="header-con" ref="searchHeightRef">
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #db2777 0%, #ea580c 100%);">
<uni-icons type="heart-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">就医次数</view>
<view class="num">{{ record.doctorCount }}<span></span></view>
</view>
</view>
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #0891b2 0%, #ec4899 100%);">
<uni-icons type="wallet-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">就医费用</view>
<view class="num">{{ record.doctorCost }}<span></span></view>
</view>
</view>
</view>
<view class="header-con" ref="searchHeightRef">
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
<uni-icons type="color" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">用药天数</view>
<view class="num">{{ record.marDayCount }}<span></span></view>
</view>
</view>
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<uni-icons type="cart-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">用药次数</view>
<view class="num">{{ record.marCount }}<span></span></view>
</view>
</view>
</view>
<view class="header-con" ref="searchHeightRef">
<view class="item">
<view class="item-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
<uni-icons type="fire-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="info-sum">
<view class="title">发烧天数</view>
<view class="num">{{ record.feverDayCount }}<span></span></view>
</view>
</view>
</view>
</view>
<!-- 视图切换 -->
<view class="section-title" v-show="listData.length>0 || secondListData.length>0">
<view class="title-decorator"></view>
<text class="title-text">档案统计</text>
<view class="view-switch">
<view
:class="['switch-item', { 'active': viewMode === 'list' }]"
@click="viewMode = 'list'; btFirstClick()"
>
<uni-icons type="list" size="16" :color="viewMode === 'list' ? '#667eea' : '#999'"></uni-icons>
<text>列表</text>
</view>
<view
:class="['switch-item', { 'active': viewMode === 'line' }]"
@click="viewMode = 'line'"
v-if="secondListData.length>0"
>
<uni-icons type="loop" size="16" :color="viewMode === 'line' ? '#667eea' : '#999'"></uni-icons>
<text>曲线图</text>
</view>
<view
:class="['switch-item', { 'active': viewMode === 'column' }]"
@click="viewMode = 'column'"
v-if="secondListData.length>0"
>
<uni-icons type="bars" size="16" :color="viewMode === 'column' ? '#667eea' : '#999'"></uni-icons>
<text>柱状图</text>
</view>
</view>
</view>
</u-sticky>
<!-- 筛选面板 -->
<u-transition :show="filterPanel" mode="fade">
<view class="filter-panel" :style="{ height: `${windowHeight - 42}px` }">
<view class="filter-panel-content">
<view class="filter-title">建档日期</view>
<view class="selcet-content" style="padding: 0 24rpx">
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
border="surround"
v-model="queryParams.startTime"
placeholder="请选择开始时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(true)"></u-icon>
</template>
</u-input>
<u-input
:disabled="true"
:disabledColor="'#fff'"
class="dateInput"
border="surround"
v-model="queryParams.endTime"
placeholder="请选择结束时间"
>
<template v-slot:suffix>
<u-icon name="calendar" @click="openOrCloseDate(false)"></u-icon>
</template>
</u-input>
</view>
</view>
<view class="btn-box">
<view class="btn-reset" @click="resetQuery()">
<uni-icons type="reload" size="16" color="#909399"></uni-icons>
<text>重置</text>
</view>
<view class="btn-confirm" @click="searchSubmit()">
<uni-icons type="checkmarkempty" size="16" color="#ffffff"></uni-icons>
<text>确定</text>
</view>
</view>
</view>
</u-transition>
<!-- 日期选择器 -->
<u-datetime-picker
:closeOnClickOverlay="true"
:show="timeShow"
v-model="time"
mode="date"
:minDate="-2209017600000"
@close="openOrCloseDate"
@cancel="openOrCloseDate"
@confirm="confirm"
></u-datetime-picker>
<!-- 曲线图展示 -->
<view class="chart-container" v-if="secondListData.length>0 && viewMode === 'line'">
<qiun-data-charts
type="line"
canvasId="healthLineChart"
:chartData="chartData"
:opts="lineChartOpts"
:loadingType="1"
/>
</view>
<!-- 柱状图展示 -->
<view class="chart-container" v-if="secondListData.length>0 && viewMode === 'column'">
<qiun-data-charts
type="column"
canvasId="healthColumnChart"
:chartData="chartData"
:opts="columnChartOpts"
:loadingType="1"
/>
</view>
<!-- 列表展示 -->
<view class="tab-switch" v-show="listData.length>0 && viewMode === 'list'">
<view
:class="['tab-item', { 'active': !tabShow }]"
@click="btFirstClick"
>
<uni-icons type="folder-add" size="16" :color="!tabShow ? '#667eea' : '#999'"></uni-icons>
<text>档案明细</text>
</view>
<view
:class="['tab-item', { 'active': tabShow }]"
@click="btSecondClick"
>
<uni-icons type="wallet" size="16" :color="tabShow ? '#667eea' : '#999'"></uni-icons>
<text>费用明细</text>
</view>
</view>
<u-list v-show="!tabShow && viewMode === 'list'" :spaceHeight="720" lowerThreshold="100">
<u-list-item v-for="(item, index) in listData" :key="index">
<view class="list-item">
<view class="item-header">
<view class="record-name-section">
<view class="record-icon">
<uni-icons type="paperplane-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="record-info">
<text class="record-name">{{ item.name }}</text>
</view>
</view>
<view class="cost-section">
<text class="cost-value"><text class="cost-label">费用</text>{{ item.doctorCost }}</text>
</view>
</view>
<view class="card-body">
<view class="info-row">
<view class="info-item">
<text class="info-label">发生时间</text>
<text class="info-value">{{ item.occurTime || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">持续时间</text>
<text class="info-value">{{ item.duration || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">过程记录</text>
<text class="info-value">{{ item.processCount || 0 }}</text>
</view>
<view class="info-item">
<text class="info-label">就医次数</text>
<text class="info-value">{{ item.doctorCount || 0 }}</text>
</view>
<view class="info-item">
<text class="info-label">用药天数</text>
<text class="info-value">{{ item.marDayCount || 0 }}</text>
</view>
<view class="info-item">
<text class="info-label">用药次数</text>
<text class="info-value">{{ item.marCount || 0 }}</text>
</view>
<view class="info-item">
<text class="info-label">发烧天数</text>
<text class="info-value">{{ item.feverDayCount || 0 }}</text>
</view>
</view>
</view>
</view>
</u-list-item>
<view>
</view>
</u-list>
<u-list v-show="tabShow && viewMode === 'list'" :spaceHeight="720" lowerThreshold="100">
<u-list-item v-for="(item, index) in secondListData" :key="index">
<view class="list-item">
<view class="item-header">
<view class="record-name-section">
<view class="record-icon">
<uni-icons type="paperplane-filled" size="20" color="#ffffff"></uni-icons>
</view>
<view class="record-info">
<text class="record-name">{{ item.time }}</text>
</view>
</view>
<view class="cost-section">
<text class="cost-value"><text class="cost-label">费用</text>{{ item.value }}</text>
</view>
</view>
</view>
</u-list-item>
<view>
</view>
</u-list>
<u-picker itemHeight="88" :show="settingPickShow" :columns="settingColumns" keyName="settingName"
@confirm="settingConfirm" @cancel="settingCancel"></u-picker>
<u-picker itemHeight="88" :show="showPerson" :columns="personList" keyName="name" @cancel="handlePersonCancel"
@confirm="handlePersonConfirm"></u-picker>
<u-picker itemHeight="88" :show="showHealthRecord" :columns="healthRecordList" keyName="name" @cancel="handleHealthRecordCancel"
@confirm="handleHealthRecordConfirm"></u-picker>
</view>
<!-- 悬停按钮返回工作台-->
<suspend></suspend>
</template>
<script setup>
import { getRecordAnalysis } from '@/api/health/statisticAnalysis'
import { listPerson } from '@/api/health/person'
import { listHealthRecord } from '@/api/health/healthRecord'
import { getDicts } from '@/api/system/dict/data.js'
import dayjs from 'dayjs'
import { timeHandler } from '@/utils/common.ts'
import {onLoad,onShow} from "@dcloudio/uni-app";
// 计算属性与监听属性是在vue中而非uniap中 需要注意!!!
import {reactive ,toRefs,ref,computed }from "vue";
const pageNum = ref(1)
const listData = ref([])
const isShow = ref(false)
const personList = ref([])
const showHealthRecord = ref(false)
const healthRecordList = ref([])
const personTypeList = ref([])
const settingPickShow = ref(false)
const settingColumns = ref([])
const showPerson = ref(false)
const timeShow= ref(false)
const time =ref( Number(new Date()))
const flag= ref(true)
const record = ref({})
const tabShow = ref(false)
const firstType = ref("primary")
const secondType = ref("default")
const secondListData = ref([])
const viewMode = ref('list') // 'list', 'line', 'column'
// 曲线图配置
const lineChartOpts = computed(() => {
const showXAxisLabel = filteredDataCount.value <= 10
return {
color: ['#667eea'],
padding: [15, 15, 30, 5],
enableScroll: false,
enableMarkLine: true,
dataLabel: false,
dataPointShape: true,
legend: {
show: false
},
xAxis: {
disableGrid: true,
boundaryGap: 'justify',
axisLine: false,
scrollShow: false,
fontSize: showXAxisLabel ? 10 : 0,
fontColor: showXAxisLabel ? '#999999' : 'transparent',
lineHeight: 12,
marginTop: 5,
disabled: !showXAxisLabel
},
yAxis: {
gridType: 'dash',
dashLength: 2,
gridColor: '#eeeeee',
padding: 10,
showTitle: false,
data: [
{
min: 0,
axisLabel: true,
fontSize: 10,
fontColor: '#999999',
format: (val) => val.toFixed(0)
}
]
},
extra: {
line: {
type: 'curve',
width: 2,
activeType: 'hollow',
linearType: 'custom',
onShadow: false,
animation: 'vertical'
},
markLine: {
type: 'dash',
dashLength: 5,
data: [
{
value: 'average',
lineColor: '#f04864',
showLabel: false,
labelOffsetX: 5,
labelOffsetY: -5
}
]
}
}
}
})
// 柱状图配置
const columnChartOpts = computed(() => {
const showXAxisLabel = filteredDataCount.value <= 10
return {
color: ['#667eea'],
padding: [15, 15, 30, 5],
enableScroll: false,
dataLabel: false,
legend: {
show: false
},
xAxis: {
disableGrid: true,
boundaryGap: 'center',
axisLine: false,
scrollShow: false,
fontSize: showXAxisLabel ? 10 : 0,
fontColor: showXAxisLabel ? '#999999' : 'transparent',
lineHeight: 12,
marginTop: 5,
disabled: !showXAxisLabel
},
yAxis: {
gridType: 'dash',
dashLength: 2,
gridColor: '#eeeeee',
padding: 10,
showTitle: false,
data: [
{
min: 0,
axisLabel: true,
fontSize: 10,
fontColor: '#999999',
format: (val) => val.toFixed(0)
}
]
},
extra: {
column: {
type: 'group',
width: 30,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true,
seriesGap: 2,
categoryGap: 2
}
}
}
})
// 图表数据
const chartData = computed(() => {
if (!secondListData.value || secondListData.value.length === 0) {
return {
categories: [],
series: []
}
}
// 使用实际的字段名time 和 value过滤掉空值和undefined
const filteredData = secondListData.value.filter(item => {
return item &&
item.time &&
item.time !== 'undefined' &&
item.time !== undefined &&
item.value !== undefined &&
item.value !== null &&
item.value !== 'undefined'
})
return {
categories: filteredData.map(item => String(item.time)),
series: [
{
name: '费用',
data: filteredData.map(item => parseFloat(item.value || 0)),
color: '#667eea',
linearColor: [
[0, '#667eea'],
[1, '#764ba2']
]
}
]
}
})
// 过滤后的实际数据量
const filteredDataCount = computed(() => {
if (!secondListData.value || secondListData.value.length === 0) {
return 0
}
return secondListData.value.filter(item => {
return item &&
item.time &&
item.time !== 'undefined' &&
item.time !== undefined &&
item.value !== undefined &&
item.value !== null &&
item.value !== 'undefined'
}).length
})
const data = reactive({
filterPanel: false,
queryPersonParams: {
pageNum: 1,
pageSize: 100
},
queryParams: {
type: 1,
time: null,
dataType: null,
recordId: null,
id: null
} ,
queryHealthRecordParams: {
pageNum: 1,
personId:null,
pageSize: 1000
}
})
const { filterPanel, queryPersonParams,queryHealthRecordParams, queryParams} = toRefs(data)
const windowHeight = computed(() => {
uni.getSystemInfoSync().windowHeight - 50
})
onLoad(() => {
getDict()
// getList()
});
onShow(() => {
if (isShow.value) {
listData.value=[]
getList()
isShow.value = false
}
});
function btFirstClick() {
tabShow.value=false
firstType.value="primary"
secondType.value="default"
}
function btSecondClick() {
secondType.value="primary"
firstType.value="default"
tabShow.value=true
}
function formatMultiLineData(data) {
if (data != null) {
return data.replace(/<br\/>/g, '\n')
}
}
function openOrCloseDate(data) {
timeShow.value = !timeShow.value
flag.value = data
}
function confirm(e) {
const date = timeHandler(new Date(e.value), '-', ':')
let formatValue = 'YYYY-MM-DD'
dayjs(date).format(formatValue)
if (flag.value) {
queryParams.value.startTime = dayjs(date).format(formatValue)
} else {
queryParams.value.endTime = dayjs(date).format(formatValue)
}
timeShow.value = false
}
function searchSubmit() {
if(queryParams.value.startTime!=''&&queryParams.value.startTime!=undefined&&queryParams.value.endTime!=''&&queryParams.value.endTime!=undefined){
queryParams.value.time = queryParams.value.startTime+'-'+queryParams.value.endTime
}
pageNum.value = 1
listData.value = []
getList()
filterPanel.value = false
}
function resetQuery() {
queryParams.value.type = '1'
queryParams.value.startTime = ''
queryParams.value.endTime = ''
queryParams.value.time = ''
}
function getList() {
getRecordAnalysis({...queryParams.value }).then(res => {
record.value = { ...res.data }
listData.value=[]
secondListData.value=[]
listData.value = listData.value.concat(res.data.recordList)
secondListData.value = secondListData.value.concat(res.data.recordCostList)
// 调试信息:查看完整返回数据和费用数据结构
console.log('完整返回数据 res.data:', res.data)
console.log('recordList:', res.data.recordList)
console.log('recordCostList:', res.data.recordCostList)
if (secondListData.value.length > 0) {
console.log('第一条费用数据示例:', secondListData.value[0])
console.log('费用数据所有字段:', Object.keys(secondListData.value[0]))
}
}).catch(() => {
})
}
function getDict() {
listPerson(queryPersonParams.value).then((response) => {
personList.value = [response.rows]
if(response.rows.length>0){
queryParams.value.personName= response.rows[0].name
queryParams.value.id = response.rows[0].id
queryHealthRecordParams.value.personId=response.rows[0].id
listHealthRecord(queryHealthRecordParams.value).then((response) => {
healthRecordList.value = [response.rows]
if(response.rows.length>0){
// queryParams.value.healthRecordName= response.rows[0].name
// queryParams.value.recordId = response.rows[0].id
}
getRecordAnalysis({...queryParams.value }).then(res => {
record.value = { ...res.data }
listData.value = listData.value.concat(res.data.recordList)
secondListData.value = secondListData.value.concat(res.data.recordCostList)
}).catch(() => {
})
})
}
})
}
function settingConfirm(e) {
queryParams.value.settingId = e.value[0].settingId
queryParams.value.settingName = e.value[0].settingName
settingPickShow.value = false
}
function settingCancel() {
settingPickShow.value = false
}
function handlePerson() {
if (personList.value[0].length === 0) {
proxy.$refs['uToast'].show({
message: '人员为空 ', type: 'warning'
})
} else {
showPerson.value = true
}
}
function handlePersonConfirm(e) {
queryParams.value.personName = e.value[0].name
queryParams.value.id= e.value[0].id
queryParams.value.healthRecordName = ''
queryParams.value.recordId = ''
queryHealthRecordParams.value.personId=e.value[0].id
listHealthRecord(queryHealthRecordParams.value).then((response) => {
healthRecordList.value = [response.rows]
showPerson.value = false
pageNum.value = 1
listData.value = []
getList()
})
}
function handlePersonCancel() {
showPerson.value = false
listData.value = []
getList()
filterPanel.value = false
}
function handleHealthRecord() {
if (healthRecordList.value[0].length === 0) {
proxy.$refs['uToast'].show({
message: '健康档案为空 ', type: 'warning'
})
} else {
showHealthRecord.value = true
}
}
function handleHealthRecordConfirm(e) {
queryParams.value.healthRecordName = e.value[0].name
queryParams.value.recordId = e.value[0].id
showHealthRecord.value = false
pageNum.value = 1
listData.value = []
getList()
}
function handleHealthRecordCancel() {
queryParams.value.healthRecordName = ''
queryParams.value.recordId = ''
showHealthRecord.value = false
listData.value = []
getList()
filterPanel.value = false
}
</script>
<style lang="scss" scoped>
page {
height: 100%;
overflow: auto;
}
.app-container {
background-color: #f5f7fa;
padding: 0;
.header-con {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8rpx 24rpx;
margin-bottom: 8rpx;
background-color: transparent;
.item {
flex: 1;
display: flex;
align-items: center;
padding: 16rpx;
background: #ffffff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
margin: 0 4rpx;
transition: all 0.3s ease;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&:active {
transform: translateY(-2rpx);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.12);
}
.item-icon {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.info-sum {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.title {
color: #7f8c8d;
margin-bottom: 4rpx;
font-size: 22rpx;
}
.num {
color: #2c3e50;
font-size: 28rpx;
font-weight: 600;
span {
font-size: 18rpx;
color: #95a5a6;
margin-left: 2rpx;
}
}
}
}
}
.header-title {
width: 100%;
height: 30px;
background-color: #ffffff;
margin-bottom: 3px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
}
}
.btnAdd {
width: 146rpx;
height: 56rpx;
line-height: 56rpx;
border-radius: 8rpx;
display:float;
text-align: center;
}
.search-view {
padding: 12rpx 32rpx;
background-color: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 100;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.search-input {
background: rgba(102, 126, 234, 0.08);
color: #333333;
flex: 1;
margin-right: 16rpx;
border-radius: 24rpx;
border: 2rpx solid rgba(102, 126, 234, 0.3);
height: 66rpx !important;
display: flex;
align-items: center;
}
.filter-btn {
display: flex;
align-items: center;
gap: 6rpx;
padding: 12rpx 24rpx;
background: rgba(102, 126, 234, 0.08);
border-radius: 24rpx;
border: 2rpx solid rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
flex-shrink: 0;
&:active {
transform: scale(0.95);
background: rgba(102, 126, 234, 0.12);
}
text {
color: #667eea;
font-size: 28rpx;
font-weight: 600;
}
uni-icons {
color: #667eea;
}
}
}
.filter-panel {
width: 100%;
position: absolute;
left: 0;
top: 270rpx;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
.filter-panel-content {
background-color: #ffffff;
padding: 0 30rpx 30rpx;
border-radius: 16rpx 16rpx 0 0;
.select-header {
color: #2c3e50;
font-size: 32rpx;
font-weight: 600;
padding: 32rpx 0 24rpx 20rpx;
position: relative;
display: flex;
align-items: center;
&::before {
content: '';
width: 6rpx;
height: 32rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 3rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
}
.filter-title {
color: #2c3e50;
font-size: 32rpx;
font-weight: 600;
padding: 32rpx 0 24rpx 20rpx;
position: relative;
display: flex;
align-items: center;
&::before {
content: '';
width: 6rpx;
height: 32rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 3rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
}
.state-list {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.state-item {
width: 210rpx;
height: 72rpx;
border: 1rpx solid rgba(0, 0, 0, 0.25);
border-radius: 72rpx;
text-align: center;
line-height: 72rpx;
margin: 0 20rpx 20rpx 0;
font-size: 28rpx;
color: #000000;
}
.active {
background-color: rgba(222, 241, 255, 1);
border: 1rpx solid rgba(22, 119, 255, 1);
}
}
}
.btn-box {
display: flex;
gap: 20rpx;
padding: 24rpx 30rpx;
background-color: #fff;
box-shadow: 0rpx -10rpx 20rpx #EEEEEE;
.btn-reset,
.btn-confirm {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
height: 88rpx;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 600;
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
text {
line-height: 1;
}
}
.btn-reset {
flex: 1;
background: #f5f7fa;
border: 2rpx solid #dcdfe6;
text {
color: #606266;
}
}
.btn-confirm {
flex: 1;
background: #667eea;
box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.2);
border: none;
text {
color: #ffffff;
}
}
}
}
.section-title {
display: flex;
margin-top: -10rpx;
align-items: center;
padding: 20rpx 32rpx 12rpx;
background-color: #f5f7fa;
.title-decorator {
width: 6rpx;
height: 32rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 3rpx;
margin-right: 12rpx;
}
.title-text {
font-size: 28rpx;
font-weight: 600;
color: #2c3e50;
line-height: 1;
flex: 1;
}
.view-switch {
display: flex;
align-items: center;
gap: 4rpx;
background: #ffffff;
border-radius: 12rpx;
padding: 4rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
.switch-item {
display: flex;
align-items: center;
gap: 4rpx;
padding: 6rpx 12rpx;
border-radius: 8rpx;
transition: all 0.3s ease;
cursor: pointer;
text {
font-size: 22rpx;
color: #999999;
line-height: 1;
}
&.active {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
text {
color: #667eea;
font-weight: 600;
}
}
&:active {
transform: scale(0.95);
}
}
}
}
.chart-container {
margin: 16rpx 24rpx 60rpx;
padding: 16rpx;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fb 100%);
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
width: calc(100% - 48rpx);
height: 480rpx;
overflow: visible;
position: relative;
}
.tab-switch {
display: flex;
align-items: center;
gap: 12rpx;
padding: 16rpx 24rpx;
background-color: #f5f7fa;
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 16rpx 24rpx;
background: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
cursor: pointer;
text {
font-size: 26rpx;
color: #999999;
font-weight: 500;
line-height: 1;
}
&.active {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border: 2rpx solid rgba(102, 126, 234, 0.3);
box-shadow: 0 2rpx 12rpx rgba(102, 126, 234, 0.2);
text {
color: #667eea;
font-weight: 600;
}
}
&:active {
transform: scale(0.98);
}
}
}
.list-item {
margin: 10rpx 24rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.2s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.06);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 24rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
.record-name-section {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
margin-right: 16rpx;
}
.record-icon {
width: 40rpx;
height: 40rpx;
background: rgba(255, 255, 255, 0.25);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
flex-shrink: 0;
}
.record-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4rpx;
.record-name {
color: #ffffff;
font-size: 28rpx;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.cost-section {
display: flex;
align-items: center;
flex-shrink: 0;
white-space: nowrap;
.cost-value {
color: #fa8c16;
font-size: 32rpx;
font-weight: 700;
line-height: 1;
white-space: nowrap;
.cost-label {
color: rgba(255, 255, 255, 0.85);
font-size: 22rpx;
font-weight: 400;
margin-right: 8rpx;
}
}
}
}
.card-body {
padding: 24rpx;
background: #fafbfc;
border: 2rpx solid #f0f2f5;
margin: 15rpx 16rpx 2rpx;
border-radius: 12rpx;
}
.info-row {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
margin-bottom: 0;
.info-item {
flex: 0 0 calc(50% - 12rpx);
display: flex;
flex-direction: column;
gap: 4rpx;
min-width: 0;
margin-bottom: -5rpx;
&.info-item-full {
flex: 0 0 100%;
}
.info-label {
font-size: 24rpx;
color: #667eea;
font-weight: 500;
background: rgba(102, 126, 234, 0.08);
padding: 6rpx 12rpx;
border-radius: 8rpx;
align-self: flex-start;
}
.info-value {
font-size: 26rpx;
color: #333;
font-weight: 500;
flex: 1;
line-height: 1.5;
word-break: break-all;
}
&:not(.info-item-full) .info-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
</style>