feat:更新运动健康模块
This commit is contained in:
@@ -1,29 +0,0 @@
|
|||||||
import { cloudService } from './index'
|
|
||||||
import { IExercise } from '@/types/health'
|
|
||||||
|
|
||||||
export const addExerciseApi = (exercise: IExercise): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/exercise',
|
|
||||||
method: 'post',
|
|
||||||
data: exercise
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateExerciseApi = (id: number, exercise: IExercise): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/home-api/exercise/${id}`,
|
|
||||||
method: 'put',
|
|
||||||
data: exercise
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteExerciseApi = (id: number): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/home-api/exercise/${id}`,
|
|
||||||
method: 'delete'
|
|
||||||
})
|
|
||||||
|
|
||||||
export const queryExerciseApi = (query: IExerciseQuery): Promise<IExercise[]> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/exercise',
|
|
||||||
method: 'get',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
16
src/apis/health.ts
Normal file
16
src/apis/health.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { cloudService } from './index'
|
||||||
|
import { IHealth, IHealthQuery } from '@/types/health'
|
||||||
|
|
||||||
|
export const updateHealthApi = (id: number, health: IHealth): Promise<boolean> =>
|
||||||
|
cloudService({
|
||||||
|
url: `/home-api/health/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data: health
|
||||||
|
})
|
||||||
|
|
||||||
|
export const queryHealthApi = (query: IHealthQuery): Promise<IHealth[]> =>
|
||||||
|
cloudService({
|
||||||
|
url: '/home-api/health',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { cloudService } from './index'
|
|
||||||
import { IWeight } from '@/types/health'
|
|
||||||
|
|
||||||
export const addWeightApi = (weight: IWeight): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/weight',
|
|
||||||
method: 'post',
|
|
||||||
data: weight
|
|
||||||
})
|
|
||||||
|
|
||||||
export const updateWeightApi = (id: number, weight: IWeight): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/home-api/weight/${id}`,
|
|
||||||
method: 'put',
|
|
||||||
data: weight
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deleteWeightApi = (id: number): Promise<boolean> =>
|
|
||||||
cloudService({
|
|
||||||
url: `/home-api/weight/${id}`,
|
|
||||||
method: 'delete'
|
|
||||||
})
|
|
||||||
|
|
||||||
export const queryWeightApi = (query: IWeightQuery): Promise<IWeight[]> =>
|
|
||||||
cloudService({
|
|
||||||
url: '/home-api/weight',
|
|
||||||
method: 'get',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bill-chart"
|
<div class="bill-chart common-chart"
|
||||||
v-loading="billStore.isLoading"
|
v-loading="billStore.isLoading"
|
||||||
element-loading-text="正在加载中...">
|
element-loading-text="正在加载中...">
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
@@ -8,19 +8,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="billDailyChartRef"/>
|
<div class="chart" ref="billDailyChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊类别统计</span>
|
<span class="title">📊类别统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="billCategoryChartRef"/>
|
<div class="chart" ref="billCategoryChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊账本统计</span>
|
<span class="title">📊账本统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="billBookChartRef"/>
|
<div class="chart" ref="billBookChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isMobile()" class="chart-container card-item">
|
|
||||||
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊排行榜</span>
|
<span class="title">📊排行榜</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,12 +31,6 @@
|
|||||||
<bill-rank />
|
<bill-rank />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="chart-container card-item">
|
|
||||||
<div class="content">
|
|
||||||
<span class="title">📊排行榜</span>
|
|
||||||
</div>
|
|
||||||
<div class="chart" ref="billRankChartRef"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -44,7 +41,6 @@ import * as echarts from 'echarts'
|
|||||||
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
||||||
import { useBillStore } from '@/store/bill.ts'
|
import { useBillStore } from '@/store/bill.ts'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import type { IStatsChart } from 'vue3-common/types'
|
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||||
|
|
||||||
const billStore = useBillStore()
|
const billStore = useBillStore()
|
||||||
@@ -53,12 +49,10 @@ const appStore = useAppStore()
|
|||||||
const billDailyChartRef = ref<HTMLElement>()
|
const billDailyChartRef = ref<HTMLElement>()
|
||||||
const billBookChartRef = ref<HTMLElement>()
|
const billBookChartRef = ref<HTMLElement>()
|
||||||
const billCategoryChartRef = ref<HTMLElement>()
|
const billCategoryChartRef = ref<HTMLElement>()
|
||||||
const billRankChartRef = ref<HTMLElement>()
|
|
||||||
|
|
||||||
let billDailyChart: echarts.ECharts
|
let billDailyChart: echarts.ECharts
|
||||||
let billBookChart: echarts.ECharts
|
let billBookChart: echarts.ECharts
|
||||||
let billCategoryChart: echarts.ECharts
|
let billCategoryChart: echarts.ECharts
|
||||||
let billRankChart: echarts.ECharts
|
|
||||||
|
|
||||||
watch(() => billStore.isRefresh, () => {
|
watch(() => billStore.isRefresh, () => {
|
||||||
updateChart()
|
updateChart()
|
||||||
@@ -68,15 +62,10 @@ onMounted(() => {
|
|||||||
billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
billDailyChart = echarts.init(billDailyChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||||
billBookChart = echarts.init(billBookChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
billBookChart = echarts.init(billBookChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||||
billCategoryChart = echarts.init(billCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
billCategoryChart = echarts.init(billCategoryChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||||
|
|
||||||
if (!isMobile()) {
|
|
||||||
billRankChart = echarts.init(billRankChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateChart = () => {
|
const updateChart = () => {
|
||||||
const MAX_CATEGORY_COUNT = 5
|
const MAX_CATEGORY_COUNT = 5
|
||||||
const MAX_RANK_COUNT = 10
|
|
||||||
|
|
||||||
// 收支统计图
|
// 收支统计图
|
||||||
const dailyXAxis = billStore.billDateStatsList.map((item) => item.name)
|
const dailyXAxis = billStore.billDateStatsList.map((item) => item.name)
|
||||||
@@ -129,19 +118,5 @@ const updateChart = () => {
|
|||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 排行榜统计图
|
|
||||||
const billRankList: IStatsChart[] = []
|
|
||||||
const resultList = billStore.billRecordList.filter((item) => item.type === billStore.billType)
|
|
||||||
resultList.sort((a, b) => b.amount - a.amount).forEach((item) => {
|
|
||||||
billRankList.push({
|
|
||||||
name: item.content,
|
|
||||||
value: item.amount
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!isMobile()) {
|
|
||||||
billRankChart.setOption(pieChartOptions('rank', '元', billRankList.slice(0, MAX_RANK_COUNT)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
:key="index" class="rank-item">
|
:key="index" class="rank-item">
|
||||||
<span class="rank-index">{{ index+1 }}.</span>
|
<span class="rank-index">{{ index+1 }}.</span>
|
||||||
<span class="rank-content">{{ item.content }}</span>
|
<span class="rank-content">{{ item.content }}</span>
|
||||||
<span class="rank-amount">¥{{ item.amount }}</span>
|
<span class="rank-amount">¥{{ formatAmount(item.amount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { useBillStore } from '@/store/bill.ts'
|
import { useBillStore } from '@/store/bill.ts'
|
||||||
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
|
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||||
|
|
||||||
const billStore = useBillStore()
|
const billStore = useBillStore()
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
<span>运动项目</span>
|
<span>运动项目</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-select v-model="healthStore.currentHealth.project"
|
<el-select v-model="healthStore.currentHealth.sportProject"
|
||||||
placeholder="请选择运行项目" style="width: 150px;">
|
placeholder="请选择运行项目" clearable style="width: 150px;">
|
||||||
<el-option v-for="(item, index) in projectList" :key="index"
|
<el-option v-for="(item, index) in projectList" :key="index"
|
||||||
:label="item" :value="item"/>
|
:label="item" :value="item"/>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
<span>运动时长</span>
|
<span>运动时长</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-input-number v-model="healthStore.currentHealth.duration"
|
<el-input-number v-model="healthStore.currentHealth.sportDuration"
|
||||||
|
:min="0" :max="300" :precision="0"
|
||||||
style="width: 120px;"></el-input-number>
|
style="width: 120px;"></el-input-number>
|
||||||
|
|
||||||
<span style="margin-left: 5px;">分钟</span>
|
<span style="margin-left: 5px;">分钟</span>
|
||||||
@@ -41,7 +42,8 @@
|
|||||||
<span>体重</span>
|
<span>体重</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-input-number v-model="healthStore.currentHealth.value"
|
<el-input-number v-model="healthStore.currentHealth.weight"
|
||||||
|
:min="0" :max="200"
|
||||||
style="width: 120px;"></el-input-number>
|
style="width: 120px;"></el-input-number>
|
||||||
|
|
||||||
<span style="margin-left: 5px;">千克</span>
|
<span style="margin-left: 5px;">千克</span>
|
||||||
@@ -59,14 +61,27 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useHealthStore } from '@/store/health'
|
import { useHealthStore } from '@/store/health'
|
||||||
import { formatDate, isBeforeDate } from 'vue3-common/utils/dateUtil'
|
import { formatDate, isBeforeDate } from 'vue3-common/utils/dateUtil'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const healthStore = useHealthStore()
|
const healthStore = useHealthStore()
|
||||||
|
|
||||||
const projectList = ['羽毛球', '乒乓球', '跑步', '其他']
|
const projectList = ['羽毛球', '乒乓球', '跑步', '其他']
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
// await periodStore.updatePeriodDay()
|
const { sportProject, sportDuration, weight } = healthStore.currentHealth
|
||||||
// periodStore.currentPeriodDay = periodStore.getPeriodDay(periodStore.selectDate)
|
if (sportProject === '' && sportDuration === 0 && weight === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sportDuration) {
|
||||||
|
if (!sportProject) {
|
||||||
|
ElMessage.error('请选择运动项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await healthStore.updateHealthDay()
|
||||||
|
healthStore.currentHealth = healthStore.getHealthDay(healthStore.selectDate)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="health-legend card-item">
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="small-circle weight"></div>
|
|
||||||
<span>体重</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="small-circle exercise"></div>
|
|
||||||
<span>运动</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.health-legend {
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.small-circle {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.weight {
|
|
||||||
background-color: #DC2626;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exercise {
|
|
||||||
background-color: #16A34A;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
23
src/components/Ledger/LedgerRank.vue
Normal file
23
src/components/Ledger/LedgerRank.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<div v-for="(item, index) in ledgerRankList.slice(0, MAX_RANK_COUNT)"
|
||||||
|
:key="index" class="rank-item">
|
||||||
|
<span class="rank-index">{{ index+1 }}.</span>
|
||||||
|
<span class="rank-content">{{ item.content }}</span>
|
||||||
|
<span class="rank-amount">¥{{ formatAmount(item.totalPrice) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLedgerStore } from '@/store/ledger.ts'
|
||||||
|
import { getLedgerSortByPrice } from '@/utils/home/ledgerUtil.ts'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||||
|
|
||||||
|
const ledgerStore = useLedgerStore()
|
||||||
|
|
||||||
|
const MAX_RANK_COUNT = 10
|
||||||
|
|
||||||
|
const ledgerRankList = computed(() => {
|
||||||
|
return getLedgerSortByPrice(ledgerStore.ledgerRecordList)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,129 +1,56 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { IHandleApi } from 'vue3-common/types'
|
import type { IHandleApi } from 'vue3-common/types'
|
||||||
|
import { queryHealthApi, updateHealthApi } from '@/apis/health.ts'
|
||||||
|
import { IHealth, IHealthQuery } from '@/types/health'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import {
|
|
||||||
addExerciseApi,
|
|
||||||
deleteExerciseApi,
|
|
||||||
queryExerciseApi,
|
|
||||||
updateExerciseApi
|
|
||||||
} from '@/apis/exercise'
|
|
||||||
import { addWeightApi, deleteWeightApi, queryWeightApi, updateWeightApi } from '@/apis/weight'
|
|
||||||
import { IExercise, IExerciseQuery, IHealth, IWeight } from '@/types/health'
|
|
||||||
|
|
||||||
export const useHealthStore = defineStore('health', {
|
export const useHealthStore = defineStore('health', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
weightRecordList: [] as IWeight[],
|
healthRecordList: [] as IHealth[],
|
||||||
exerciseRecordList: [] as IExercise[],
|
|
||||||
queryInfo: {
|
queryInfo: {
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: ''
|
endDate: ''
|
||||||
} as IExerciseQuery,
|
} as IHealthQuery,
|
||||||
weightApiType: 'ADD' as IHandleApi,
|
healthApiType: 'ADD' as IHandleApi,
|
||||||
exerciseApiType: 'ADD' as IHandleApi,
|
|
||||||
currentExercise: {
|
|
||||||
project: '',
|
|
||||||
duration: 0,
|
|
||||||
date: ''
|
|
||||||
} as IExercise,
|
|
||||||
currentHealth: {
|
currentHealth: {
|
||||||
project: '',
|
sportProject: '',
|
||||||
duration: 0,
|
sportDuration: 0,
|
||||||
value: 0,
|
weight: 0,
|
||||||
date: ''
|
date: ''
|
||||||
} as IHealth,
|
} as IHealth,
|
||||||
currentWeight: {
|
|
||||||
value: '',
|
|
||||||
date: ''
|
|
||||||
} as IWeight,
|
|
||||||
selectDate: '',
|
selectDate: '',
|
||||||
isScrollEnd: false,
|
isScrollEnd: false,
|
||||||
isRefresh: false
|
isRefresh: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async queryExerciseRecordList() {
|
async queryHealthRecordList() {
|
||||||
this.exerciseRecordList = await queryExerciseApi(this.queryInfo)
|
this.healthRecordList = await queryHealthApi(this.queryInfo)
|
||||||
},
|
},
|
||||||
async queryWeightRecordList() {
|
async updateHealthDay() {
|
||||||
this.weightRecordList = await queryWeightApi(this.queryInfo)
|
await updateHealthApi(this.currentHealth.id as number, this.currentHealth)
|
||||||
},
|
|
||||||
async handleExerciseApi(exercise: IExercise) {
|
|
||||||
switch (this.exerciseApiType) {
|
|
||||||
case 'ADD':
|
|
||||||
await addExerciseApi(exercise)
|
|
||||||
ElMessage.success('新增锻炼记录成功')
|
|
||||||
break
|
|
||||||
case 'UPDATE':
|
|
||||||
await updateExerciseApi(exercise.id as number, exercise)
|
|
||||||
ElMessage.success('更新锻炼记录成功')
|
|
||||||
break
|
|
||||||
case 'DELETE':
|
|
||||||
await deleteExerciseApi(exercise.id as number)
|
|
||||||
ElMessage.success('删除锻炼记录成功')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await this.refreshInfo()
|
|
||||||
},
|
|
||||||
async handleWeightApi(weight: IWeight) {
|
|
||||||
switch (this.weightApiType) {
|
|
||||||
case 'ADD':
|
|
||||||
await addWeightApi(weight)
|
|
||||||
ElMessage.success('新增体重记录成功')
|
|
||||||
break
|
|
||||||
case 'UPDATE':
|
|
||||||
await updateWeightApi(weight.id as number, weight)
|
|
||||||
ElMessage.success('更新体重记录成功')
|
|
||||||
break
|
|
||||||
case 'DELETE':
|
|
||||||
await deleteWeightApi(weight.id as number)
|
|
||||||
ElMessage.success('删除体重记录成功')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await this.refreshInfo()
|
await this.refreshInfo()
|
||||||
|
ElMessage.success('更新记录成功')
|
||||||
},
|
},
|
||||||
getHealthDay(date: string): IHealth {
|
getHealthDay(date: string): IHealth {
|
||||||
const exercise = this.exerciseRecordList.find((item) => item.date === date)
|
const result = this.healthRecordList.find((item) => item.date === date)
|
||||||
const weight = this.weightRecordList.find((item) => item.date === date)
|
|
||||||
this.currentHealth = this.getEmptyHealth()
|
|
||||||
|
|
||||||
if (exercise) {
|
if (result) {
|
||||||
this.currentHealth.project = exercise.project
|
return result
|
||||||
this.currentHealth.duration = exercise.duration
|
} else {
|
||||||
|
return this.getEmptyHealth(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weight) {
|
|
||||||
this.currentHealth.value = weight.value
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentHealth.date = date
|
|
||||||
},
|
},
|
||||||
async refreshInfo() {
|
async refreshInfo() {
|
||||||
await this.queryExerciseRecordList()
|
await this.queryHealthRecordList()
|
||||||
await this.queryWeightRecordList()
|
|
||||||
this.isRefresh = !this.isRefresh
|
this.isRefresh = !this.isRefresh
|
||||||
},
|
},
|
||||||
getEmptyExercise(): IExercise {
|
getEmptyHealth(date: string): IHealth {
|
||||||
return {
|
return {
|
||||||
project: '',
|
id: 0,
|
||||||
duration: 0,
|
date,
|
||||||
date: ''
|
sportDuration: 0,
|
||||||
}
|
sportProject: '',
|
||||||
},
|
weight: 0
|
||||||
getEmptyWeight(): IWeight {
|
|
||||||
return {
|
|
||||||
value: 0,
|
|
||||||
date: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getEmptyHealth(): IHealth {
|
|
||||||
return {
|
|
||||||
date: '',
|
|
||||||
duration: 0,
|
|
||||||
project: '',
|
|
||||||
value: 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
updatePeriodSettingApi
|
updatePeriodSettingApi
|
||||||
} from '@/apis/period.ts'
|
} from '@/apis/period.ts'
|
||||||
import { getCurrentMonth } from 'vue3-common/utils/dateUtil'
|
import { getCurrentMonth } from 'vue3-common/utils/dateUtil'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
export const usePeriodStore = defineStore('period', {
|
export const usePeriodStore = defineStore('period', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -43,6 +44,7 @@ export const usePeriodStore = defineStore('period', {
|
|||||||
async updatePeriodDay() {
|
async updatePeriodDay() {
|
||||||
await updatePeriodDayApi(this.currentPeriodDay.id as number, this.currentPeriodDay)
|
await updatePeriodDayApi(this.currentPeriodDay.id as number, this.currentPeriodDay)
|
||||||
await this.refreshInfo()
|
await this.refreshInfo()
|
||||||
|
ElMessage.success('更新记录成功')
|
||||||
},
|
},
|
||||||
async queryPeriodSetting() {
|
async queryPeriodSetting() {
|
||||||
this.periodSetting = await queryPeriodSettingApi()
|
this.periodSetting = await queryPeriodSettingApi()
|
||||||
|
|||||||
@@ -7,66 +7,6 @@
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bill-chart {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: repeat(4, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 40px 1fr;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 0 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #009FA8;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
height: 300px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-chart {
|
|
||||||
overflow: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
.rank-item {
|
|
||||||
padding: 10px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 20px 1fr 80px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.rank-amount {
|
|
||||||
justify-self: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-item:nth-child(1) {
|
|
||||||
color: #d4af37;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-item:nth-child(2) {
|
|
||||||
color: #a8a8a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rank-item:nth-child(3) {
|
|
||||||
color: #b87333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-switch {
|
.el-switch {
|
||||||
.el-switch__core {
|
.el-switch__core {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
.bill-view {
|
|
||||||
.bill-chart {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
gap: 2vh;
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 40px 1fr;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 0 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #009FA8;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.bill-chart {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
gap: 2vh;
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
.chart {
|
|
||||||
min-width: 300px;
|
|
||||||
min-height: 250px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bill-dialog {
|
|
||||||
width: 950px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,6 +52,67 @@ $tabBar-height: 70px;
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.common-chart {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 40px 1fr;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #009FA8;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-chart {
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.rank-item {
|
||||||
|
padding: 10px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20px 1fr 80px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.rank-amount {
|
||||||
|
justify-self: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-item:nth-child(1) {
|
||||||
|
color: #d4af37;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-item:nth-child(2) {
|
||||||
|
color: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-item:nth-child(3) {
|
||||||
|
color: #b87333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.title-bar {
|
.title-bar {
|
||||||
background-color: var(--el-color-primary);
|
background-color: var(--el-color-primary);
|
||||||
height: $titleBar-height;
|
height: $titleBar-height;
|
||||||
|
|||||||
@@ -22,3 +22,7 @@ export const statsByField = (records: ILedgerRecord[],
|
|||||||
.sort((a, b) => b.value - a.value)
|
.sort((a, b) => b.value - a.value)
|
||||||
.slice(0, 5)
|
.slice(0, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getLedgerSortByPrice = (records: ILedgerRecord[]) => {
|
||||||
|
return records.sort((a, b) => b?.totalPrice - a?.totalPrice)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,21 +4,19 @@
|
|||||||
@dayclick="dateClick" @did-move="changeMonthClick"
|
@dayclick="dateClick" @did-move="changeMonthClick"
|
||||||
:is-dark="appStore.isDark"
|
:is-dark="appStore.isDark"
|
||||||
class="health-calendar card-item"/>
|
class="health-calendar card-item"/>
|
||||||
<health-legend />
|
|
||||||
|
|
||||||
<health-form />
|
<health-form />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import HealthLegend from '@/components/Health/HealthLegend.vue'
|
|
||||||
import HealthForm from '@/components/Health/HealthForm.vue'
|
import HealthForm from '@/components/Health/HealthForm.vue'
|
||||||
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
|
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
|
||||||
import { formatDate, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
import { formatDate, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
||||||
import { onMounted, reactive } from 'vue'
|
import { onMounted, reactive, watch } from 'vue'
|
||||||
import { useHealthStore } from '@/store/health'
|
import { useHealthStore } from '@/store/health'
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { IExercise, IWeight } from '@/types/health'
|
import { IHealth } from '@/types/health'
|
||||||
|
|
||||||
const healthStore = useHealthStore()
|
const healthStore = useHealthStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
@@ -28,24 +26,22 @@ const dailyInfo = reactive({
|
|||||||
attrs: [] as any[]
|
attrs: [] as any[]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => healthStore.isRefresh, () => {
|
||||||
|
dailyInfo.attrs = []
|
||||||
|
updateRecordList()
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const result = getStartEndDate(formatDate(new Date()), 'month')
|
const result = getStartEndDate(formatDate(new Date()), 'month')
|
||||||
healthStore.queryInfo = { startDate: result[0], endDate: result[1] }
|
healthStore.queryInfo = { startDate: result[0], endDate: result[1] }
|
||||||
|
|
||||||
await healthStore.refreshInfo()
|
await healthStore.refreshInfo()
|
||||||
updateRecordList()
|
healthStore.selectDate = formatDate(new Date())
|
||||||
healthStore.getHealthDay(formatDate(new Date()))
|
healthStore.currentHealth = healthStore.getHealthDay(formatDate(new Date()))
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateRecordList = () => {
|
const updateRecordList = () => {
|
||||||
healthStore.weightRecordList.forEach((item: IWeight) => {
|
healthStore.healthRecordList.forEach((item: IHealth) => {
|
||||||
dailyInfo.attrs.push({
|
|
||||||
dot: 'red',
|
|
||||||
dates: item.date
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
healthStore.exerciseRecordList.forEach((item: IExercise) => {
|
|
||||||
dailyInfo.attrs.push({
|
dailyInfo.attrs.push({
|
||||||
dot: 'green',
|
dot: 'green',
|
||||||
dates: item.date
|
dates: item.date
|
||||||
@@ -55,7 +51,7 @@ const updateRecordList = () => {
|
|||||||
|
|
||||||
const dateClick = (day: CalendarDay) => {
|
const dateClick = (day: CalendarDay) => {
|
||||||
healthStore.selectDate = formatDate(day.date)
|
healthStore.selectDate = formatDate(day.date)
|
||||||
healthStore.getHealthDay(formatDate(day.date))
|
healthStore.currentHealth = healthStore.getHealthDay(formatDate(day.date))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-ledger-stats common-mobile-view">
|
<div class="mobile-ledger-stats common-mobile-view">
|
||||||
<div class="ledger-chart">
|
<div class="ledger-chart common-chart">
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊类别统计</span>
|
<span class="title">📊类别统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="ledgerCategoryChartRef"/>
|
<div class="chart" ref="ledgerCategoryChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chart-container card-item">
|
<div class="chart-container card-item">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="title">📊付款人统计</span>
|
<span class="title">📊付款人统计</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart" ref="ledgerPayerChartRef"/>
|
<div class="chart" ref="ledgerPayerChartRef"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-container card-item">
|
||||||
|
<div class="content">
|
||||||
|
<span class="title">📊排行榜</span>
|
||||||
|
</div>
|
||||||
|
<div class="chart rank-chart">
|
||||||
|
<ledger-rank />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import LedgerRank from '@/components/Ledger/LedgerRank.vue'
|
||||||
import { useLedgerStore } from '@/store/ledger.ts'
|
import { useLedgerStore } from '@/store/ledger.ts'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { statsByField } from '@/utils/home/ledgerUtil.ts'
|
import { statsByField } from '@/utils/home/ledgerUtil.ts'
|
||||||
@@ -70,37 +81,3 @@ const updateChart = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.mobile-ledger-stats {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.ledger-chart {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: repeat(2, 1fr);
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 40px 1fr;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 0 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #009FA8;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user