feat:重构模块
This commit is contained in:
166
src/components/Bill/BillBook.vue
Normal file
166
src/components/Bill/BillBook.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="bill-setting">
|
||||
<div class="button-container">
|
||||
<el-button type="primary" @click="addNewBookClick">新增账本</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="billStore.billBookList"
|
||||
border stripe
|
||||
:header-cell-style="tableHeaderStyle()"
|
||||
:cell-style="tableCellStyle()"
|
||||
:row-style="tableRowStyle()"
|
||||
style="width: 100%"
|
||||
class="card-item">
|
||||
<el-table-column type="index" align="center" width="50"/>
|
||||
<el-table-column prop="name" label="名称" align="center" width="100"/>
|
||||
<el-table-column label="图标" align="center" width="80">
|
||||
<template #default="scope">
|
||||
<i :class="scope.row.icon" :style="{color: scope.row.color, fontSize: '24px'}"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" @click="editBookClick(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteBookClick(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="billInfo.billBookDialog.visible"
|
||||
:title="billInfo.billBookDialog.title"
|
||||
center width="80%" @open="openDialogEvent">
|
||||
<el-form ref="formRef" :model="billInfo.billBookDialog.data"
|
||||
:rules="rules" label-position="top" label-width="100px">
|
||||
<el-form-item label="账本名称" prop="name">
|
||||
<el-input v-model="billInfo.billBookDialog.data.name" autocomplete="off"
|
||||
placeholder="请输入类型名称"
|
||||
:maxlength="10" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型图标" prop="icon">
|
||||
<el-input v-model="billInfo.billBookDialog.data.icon" autocomplete="off"
|
||||
placeholder="请输入类型图标"
|
||||
:maxlength="40" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标颜色" prop="color">
|
||||
<el-color-picker v-model="billInfo.billBookDialog.data.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标预览">
|
||||
<i :class="billInfo.billBookDialog.data.icon"
|
||||
:style="{color: billInfo.billBookDialog.data.color, fontSize: '24px'}"></i>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelClick">取消</el-button>
|
||||
<el-button type="primary" @click="confirmClick">确认</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { IBillBook } from '@/types/bill.ts'
|
||||
import { billBookRules } from '@/utils/element/elRules.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
||||
import { addBillBookApi, updateBillBookApi } from '@/apis/bill.ts'
|
||||
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
|
||||
|
||||
const formRef = ref()
|
||||
const rules = reactive<FormRules>(billBookRules)
|
||||
const billStore = useBillStore()
|
||||
|
||||
const billInfo = reactive({
|
||||
setBillBookType: 'ADD' as IHandleApi,
|
||||
billBookDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
data: {}
|
||||
} as IDialog<IBillBook>
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await billStore.queryBillBook()
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击新增账本按钮
|
||||
*/
|
||||
const addNewBookClick = () => {
|
||||
billInfo.setBillBookType = 'ADD'
|
||||
billInfo.billBookDialog = {
|
||||
title: '新增账本',
|
||||
visible: true,
|
||||
data: {
|
||||
name: '',
|
||||
icon: '',
|
||||
color: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击编辑账本按钮
|
||||
* @param book 账本
|
||||
*/
|
||||
const editBookClick = async (book: IBillBook) => {
|
||||
billInfo.setBillBookType = 'UPDATE'
|
||||
billInfo.billBookDialog = {
|
||||
title: '编辑账本',
|
||||
visible: true,
|
||||
data: deepCopyObject(book)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击删除账本按钮
|
||||
* @param book 账本
|
||||
*/
|
||||
const deleteBookClick = (book: IBillBook) => {
|
||||
billInfo.setBillBookType = 'DELETE'
|
||||
ElMessage.error('暂不支持该功能')
|
||||
}
|
||||
|
||||
const openDialogEvent = () => {
|
||||
nextTick(() => {
|
||||
formRef.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
const setBillBookApi = async (id?: number) => {
|
||||
switch (billInfo.setBillBookType) {
|
||||
case 'ADD':
|
||||
await addBillBookApi(billInfo.billBookDialog.data)
|
||||
ElMessage.success('新增账本成功')
|
||||
break
|
||||
case 'UPDATE':
|
||||
await updateBillBookApi(id as number, billInfo.billBookDialog.data)
|
||||
ElMessage.success('更新账本成功')
|
||||
break
|
||||
case 'DELETE':
|
||||
ElMessage.success('删除账本成功')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
await billStore.queryBillBook()
|
||||
billInfo.billBookDialog.visible = false
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
billInfo.billBookDialog.visible = false
|
||||
}
|
||||
|
||||
const confirmClick = async () => {
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
await setBillBookApi(billInfo.billBookDialog.data.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
109
src/components/Bill/BillCalendar.vue
Normal file
109
src/components/Bill/BillCalendar.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div v-loading="billStore.isLoading"
|
||||
element-loading-text="正在加载中..."
|
||||
class="bill-daily">
|
||||
<v-calendar ref="calendarRef"
|
||||
:attributes='dailyInfo.attrs' :is-dark="appStore.isDark"
|
||||
@dayclick="dateClick" @did-move="changeMonthClick"
|
||||
class="bill-calendar card-item"/>
|
||||
|
||||
<bill-daily-item :date="dailyInfo.calendarDate"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BillDailyItem from '@/components/Bill/BillDailyItem.vue'
|
||||
import { reactive, watch, ref, onMounted } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { CalendarDay, Page } from 'v-calendar/dist/types/src/utils/page'
|
||||
import { getCalendarDateList } from '@/utils/home/billUtil.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
|
||||
const calendarRef = ref()
|
||||
const billStore = useBillStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
onMounted(() => {
|
||||
const { billMonth } = billStore.billQueryForm
|
||||
calendarRef.value.move(new Date(billMonth))
|
||||
})
|
||||
|
||||
watch(() => billStore.isRefresh, () => {
|
||||
updateInfo()
|
||||
})
|
||||
|
||||
const dailyInfo = reactive({
|
||||
calendarDate: '',
|
||||
attrs: [] as any[]
|
||||
})
|
||||
|
||||
const updateInfo = () => {
|
||||
getCalendarMoveDate()
|
||||
getCalendarAttrDates()
|
||||
}
|
||||
|
||||
const getCalendarMoveDate = () => {
|
||||
const billLength = billStore.billRecordList.length
|
||||
if (billLength > 0) {
|
||||
// 如果包含今日日期 则显示今日 否则显示当月最近的日期
|
||||
const today = formatDate(new Date())
|
||||
if (billStore.billRecordList.some((item) => item.date === today)) {
|
||||
dailyInfo.calendarDate = today
|
||||
} else {
|
||||
dailyInfo.calendarDate = formatDate(billStore.billRecordList[0].date)
|
||||
}
|
||||
} else {
|
||||
dailyInfo.calendarDate = ''
|
||||
}
|
||||
}
|
||||
|
||||
const getCalendarAttrDates = () => {
|
||||
dailyInfo.attrs = []
|
||||
dailyInfo.attrs.push({
|
||||
key: 'today',
|
||||
highlight: true,
|
||||
dates: new Date()
|
||||
})
|
||||
dailyInfo.attrs.push({
|
||||
dot: 'red',
|
||||
dates: getCalendarDateList(billStore.billRecordList, 'expensive')
|
||||
})
|
||||
dailyInfo.attrs.push({
|
||||
dot: 'green',
|
||||
dates: getCalendarDateList(billStore.billRecordList, 'income')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择日期事件
|
||||
* @param day 日期
|
||||
*/
|
||||
const dateClick = (day: CalendarDay) => {
|
||||
dailyInfo.calendarDate = formatDate(day.date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择月份事件
|
||||
* @param value 值
|
||||
*/
|
||||
const changeMonthClick = async (value: Page[]) => {
|
||||
billStore.billQueryForm.billMonth = value[0].id
|
||||
await billStore.refreshInfo()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.bill-daily {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
|
||||
.bill-calendar {
|
||||
width: 100%;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
222
src/components/Bill/BillCategory.vue
Normal file
222
src/components/Bill/BillCategory.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div class="bill-setting">
|
||||
<div class="button-container">
|
||||
<el-select
|
||||
v-model="billInfo.bookName"
|
||||
placeholder="请选择账本名称"
|
||||
@focus="focusBookNameEvent"
|
||||
@change="selectBookNameEvent"
|
||||
style="width: 150px;">
|
||||
<el-option
|
||||
v-for="(item, index) in billInfo.billBookList"
|
||||
:key="index" :label="item.name" :value="item.name"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-button type="primary" @click="addNewCategoryClick">新增类别</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="billInfo.billCategoryList"
|
||||
border stripe
|
||||
:header-cell-style="tableHeaderStyle()"
|
||||
:cell-style="tableCellStyle()"
|
||||
:row-style="tableRowStyle()"
|
||||
class="card-item">
|
||||
<el-table-column type="index" align="center" width="50"/>
|
||||
<el-table-column prop="name" label="名称" align="center" width="80"/>
|
||||
<el-table-column label="类型" align="center" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.type === 'expensive'" effect="dark" color="#CA6C65">支出</el-tag>
|
||||
<el-tag v-else effect="dark" color="#6FBB69">收入</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="图标" align="center" width="80">
|
||||
<template #default="scope">
|
||||
<i :class="scope.row.icon" :style="{color: scope.row.color, fontSize: '24px'}"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" @click="editCategoryClick(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteCategoryClick(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="billInfo.billCategoryDialog.visible"
|
||||
:title="billInfo.billCategoryDialog.title"
|
||||
center width="80%" @open="openDialogEvent">
|
||||
<el-form ref="formRef" :model="billInfo.billCategoryDialog.data"
|
||||
:rules="rules" label-position="top" label-width="100px">
|
||||
<el-form-item label="账本名称" prop="bookName">
|
||||
<el-select
|
||||
v-model="billInfo.billCategoryDialog.data.bookName"
|
||||
placeholder="请选择账本名称"
|
||||
@focus="focusBookNameEvent">
|
||||
<el-option
|
||||
v-for="(item, index) in billInfo.billBookList"
|
||||
:key="index" :label="item.name" :value="item.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别名称" prop="name">
|
||||
<el-input v-model="billInfo.billCategoryDialog.data.name" autocomplete="off"
|
||||
placeholder="请输入类型名称"
|
||||
:maxlength="10" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型名称" prop="type">
|
||||
<el-radio-group v-model="billInfo.billCategoryDialog.data.type">
|
||||
<el-radio value="expensive">支出</el-radio>
|
||||
<el-radio value="income">收入</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="类别图标" prop="icon">
|
||||
<el-input v-model="billInfo.billCategoryDialog.data.icon" autocomplete="off"
|
||||
placeholder="请输入类型图标"
|
||||
:maxlength="40" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标颜色" prop="color">
|
||||
<el-color-picker v-model="billInfo.billCategoryDialog.data.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标预览">
|
||||
<i :class="billInfo.billCategoryDialog.data.icon"
|
||||
:style="{color: billInfo.billCategoryDialog.data.color, fontSize: '24px'}"></i>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelClick">取消</el-button>
|
||||
<el-button type="primary" @click="confirmClick">确认</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { IBillBook, IBillCategory } from '@/types/bill.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { billCategoryRules } from '@/utils/element/elRules.ts'
|
||||
import { addBillCategoryApi, updateBillCategoryApi } from '@/apis/bill.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { tableHeaderStyle, tableRowStyle, tableCellStyle } from 'vue3-common/utils/elUtil'
|
||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
||||
|
||||
const formRef = ref()
|
||||
const rules = reactive<FormRules>(billCategoryRules)
|
||||
const billStore = useBillStore()
|
||||
|
||||
const billInfo = reactive({
|
||||
bookName: '日常账本',
|
||||
setBillCategoryType: 'ADD' as IHandleApi,
|
||||
billCategoryDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
data: {}
|
||||
} as IDialog<IBillCategory>,
|
||||
billBookList: [] as IBillBook[],
|
||||
billCategoryList: [] as IBillCategory[]
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await billStore.queryBillCategory()
|
||||
queryBookCategoryByBookName(billInfo.bookName)
|
||||
})
|
||||
|
||||
const selectBookNameEvent = (value: string) => {
|
||||
queryBookCategoryByBookName(value)
|
||||
}
|
||||
|
||||
const queryBookCategoryByBookName = (bookName: string) => {
|
||||
billInfo.billCategoryList = billStore.billCategoryList.filter((item) => {
|
||||
return item.bookName === bookName
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击新增类别按钮
|
||||
*/
|
||||
const addNewCategoryClick = () => {
|
||||
billInfo.setBillCategoryType = 'ADD'
|
||||
billInfo.billCategoryDialog = {
|
||||
title: '新增账单类别',
|
||||
visible: true,
|
||||
data: {
|
||||
bookName: '',
|
||||
name: '',
|
||||
type: '',
|
||||
icon: '',
|
||||
color: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击编辑类别按钮
|
||||
* @param category 类别
|
||||
*/
|
||||
const editCategoryClick = async (category: IBillCategory) => {
|
||||
billInfo.setBillCategoryType = 'UPDATE'
|
||||
billInfo.billCategoryDialog = {
|
||||
title: '编辑账单类别',
|
||||
visible: true,
|
||||
data: deepCopyObject(category)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击删除类别按钮
|
||||
* @param category 类别
|
||||
*/
|
||||
const deleteCategoryClick = (category: IBillCategory) => {
|
||||
billInfo.setBillCategoryType = 'DELETE'
|
||||
ElMessage.error('暂不支持该功能')
|
||||
}
|
||||
|
||||
const openDialogEvent = () => {
|
||||
nextTick(() => {
|
||||
formRef.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
const focusBookNameEvent = async () => {
|
||||
await billStore.queryBillBook()
|
||||
billInfo.billBookList = billStore.billBookList
|
||||
}
|
||||
|
||||
const setBillCategoryApi = async (id?: number) => {
|
||||
switch (billInfo.setBillCategoryType) {
|
||||
case 'ADD':
|
||||
await addBillCategoryApi(billInfo.billCategoryDialog.data)
|
||||
ElMessage.success('新增账单类别成功')
|
||||
break
|
||||
case 'UPDATE':
|
||||
await updateBillCategoryApi(id as number, billInfo.billCategoryDialog.data)
|
||||
ElMessage.success('更新账单类别成功')
|
||||
break
|
||||
case 'DELETE':
|
||||
ElMessage.success('删除账单类别成功')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
await billStore.queryBillCategory()
|
||||
billInfo.billCategoryDialog.visible = false
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
billInfo.billCategoryDialog.visible = false
|
||||
}
|
||||
|
||||
const confirmClick = async () => {
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
await setBillCategoryApi(billInfo.billCategoryDialog.data.id)
|
||||
queryBookCategoryByBookName(billInfo.bookName)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
147
src/components/Bill/BillChart.vue
Normal file
147
src/components/Bill/BillChart.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="bill-chart"
|
||||
v-loading="billStore.isLoading"
|
||||
element-loading-text="正在加载中...">
|
||||
<div class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊收支统计</span>
|
||||
</div>
|
||||
<div class="chart" ref="billDailyChartRef"/>
|
||||
</div>
|
||||
<div class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊类别统计</span>
|
||||
</div>
|
||||
<div class="chart" ref="billCategoryChartRef"/>
|
||||
</div>
|
||||
<div class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊账本统计</span>
|
||||
</div>
|
||||
<div class="chart" ref="billBookChartRef"/>
|
||||
</div>
|
||||
<div v-if="isMobile()" class="chart-container card-item">
|
||||
<div class="content">
|
||||
<span class="title">📊排行榜</span>
|
||||
</div>
|
||||
<div class="chart rank-chart">
|
||||
<bill-rank />
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BillRank from '@/components/Bill/BillRank.vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import { lineChartOptions, barChartOptions, pieChartOptions } from 'vue3-common/utils/eChartsUtil'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import type { IStatsChart } from 'vue3-common/types'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
|
||||
const billStore = useBillStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const billDailyChartRef = ref<HTMLElement>()
|
||||
const billBookChartRef = ref<HTMLElement>()
|
||||
const billCategoryChartRef = ref<HTMLElement>()
|
||||
const billRankChartRef = ref<HTMLElement>()
|
||||
|
||||
let billDailyChart: echarts.ECharts
|
||||
let billBookChart: echarts.ECharts
|
||||
let billCategoryChart: echarts.ECharts
|
||||
let billRankChart: echarts.ECharts
|
||||
|
||||
watch(() => billStore.isRefresh, () => {
|
||||
updateChart()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
billDailyChart = echarts.init(billDailyChartRef.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)
|
||||
|
||||
if (!isMobile()) {
|
||||
billRankChart = echarts.init(billRankChartRef.value as HTMLElement, appStore.isDark ? 'dark' : null)
|
||||
}
|
||||
})
|
||||
|
||||
const updateChart = () => {
|
||||
const MAX_CATEGORY_COUNT = 5
|
||||
const MAX_RANK_COUNT = 10
|
||||
|
||||
// 收支统计图
|
||||
const dailyXAxis = billStore.billDateStatsList.map((item) => item.name)
|
||||
const dailyYAxis = billStore.billDateStatsList.map((item) => item.value)
|
||||
billDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '金额(元)', dailyYAxis))
|
||||
billDailyChart.setOption({
|
||||
series: [{
|
||||
itemStyle: {
|
||||
color: billStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
|
||||
},
|
||||
markPoint: {
|
||||
label: {
|
||||
color: '#FFFFFF'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
// 账本统计图
|
||||
const bookXAxis = billStore.billBookStatsList.map((item) => item.name)
|
||||
const bookYAxis = billStore.billBookStatsList.map((item) => item.value)
|
||||
billBookChart.setOption(barChartOptions('账本', bookXAxis, '金额(元)', bookYAxis))
|
||||
billBookChart.setOption({
|
||||
series: [{
|
||||
itemStyle: {
|
||||
color: billStore.billType === 'expensive' ? '#CA6C65' : '#6FBB69'
|
||||
},
|
||||
markPoint: {
|
||||
label: {
|
||||
color: '#FFFFFF'
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
// 类别统计图
|
||||
const billCategoryList = billStore.billCategoryStatsList.slice(0, MAX_CATEGORY_COUNT)
|
||||
billCategoryChart.setOption(pieChartOptions('rank', '元', billCategoryList))
|
||||
|
||||
if (isMobile()) {
|
||||
billCategoryChart.setOption({
|
||||
series: [{
|
||||
radius: ['20%', '50%'],
|
||||
label: {
|
||||
formatter: '{b}: {d}%'
|
||||
}
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 排行榜统计图
|
||||
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>
|
||||
134
src/components/Bill/BillDailyItem.vue
Normal file
134
src/components/Bill/BillDailyItem.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<el-empty v-if="dailyItemInfo.itemList.length === 0"/>
|
||||
|
||||
<div v-else class="bill-daily-item card-item">
|
||||
<daily-summary :date="dailyItemInfo.date">
|
||||
<template #extra>
|
||||
<span>收入:{{ formatAmount(dailyItemInfo.totalIncome, true) }}</span>
|
||||
<span>支出:{{ formatAmount(dailyItemInfo.totalExpensive, true) }}</span>
|
||||
</template>
|
||||
</daily-summary>
|
||||
|
||||
<div class="daily-list">
|
||||
<daily-card v-for="(item, index) in dailyItemInfo.itemList"
|
||||
:key="index" @click="selectBillRecordClick(item)"
|
||||
:title="item.category" :content="item.content">
|
||||
<template #icon>
|
||||
<i :class="getCategoryByName(billStore.billCategoryList, item.category).icon"
|
||||
:style="{color: getCategoryByName(billStore.billCategoryList, item.category).color}">
|
||||
</i>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<span v-if="item.location" class="location">
|
||||
<i class="fa-solid fa-location-dot"></i>
|
||||
{{ item.location }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #extra>
|
||||
<span>{{ formatAmount(item.amount, true) }}</span>
|
||||
<span>{{ item.payAccount }}</span>
|
||||
</template>
|
||||
</daily-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DailySummary from '@/components/DailySummary.vue'
|
||||
import DailyCard from '@/components/DailyCard.vue'
|
||||
import { defineProps, onMounted, reactive, watch } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
||||
import { IBillRecord } from '@/types/bill.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
import { getCalendarDailyCount, getCategoryByName } from '@/utils/home/billUtil.ts'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
|
||||
const billStore = useBillStore()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
date: {
|
||||
required: true,
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.date, () => {
|
||||
getBillDailyByDate(props.date as string)
|
||||
})
|
||||
|
||||
watch(() => billStore.isRefresh, () => {
|
||||
getBillDailyByDate(props.date as string)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getBillDailyByDate(props.date as string)
|
||||
})
|
||||
|
||||
const dailyItemInfo = reactive({
|
||||
date: '',
|
||||
totalExpensive: 0,
|
||||
totalIncome: 0,
|
||||
itemList: [] as IBillRecord[]
|
||||
})
|
||||
|
||||
/**
|
||||
* 根据日期获取当天的收支记录
|
||||
* @param date
|
||||
*/
|
||||
const getBillDailyByDate = (date: string) => {
|
||||
if (date) {
|
||||
dailyItemInfo.date = formatChineseDate(date, 'MM月DD日 dddd')
|
||||
|
||||
const calendarDaily = getCalendarDailyCount(billStore.billRecordList, date)
|
||||
dailyItemInfo.itemList = calendarDaily.dailyRecordList
|
||||
dailyItemInfo.totalIncome = calendarDaily.totalIncome
|
||||
dailyItemInfo.totalExpensive = calendarDaily.totalExpensive
|
||||
} else {
|
||||
dailyItemInfo.itemList = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击账单记录按钮
|
||||
* @param billRecord 账单记录
|
||||
*/
|
||||
const selectBillRecordClick = async (billRecord: IBillRecord) => {
|
||||
await billStore.queryBillRecord(billRecord.id as number)
|
||||
billStore.billApiType = 'UPDATE'
|
||||
|
||||
if (isMobile()) {
|
||||
await router.push({
|
||||
name: 'BillDetailId',
|
||||
params: { id: billStore.currentBillRecord.id }
|
||||
})
|
||||
} else {
|
||||
billStore.billDialog = {
|
||||
title: '编辑账单',
|
||||
visible: true,
|
||||
data: deepCopyObject(billStore.currentBillRecord)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.bill-daily-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
padding: 1vh 0.5vw;
|
||||
|
||||
.daily-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
59
src/components/Bill/BillDailyList.vue
Normal file
59
src/components/Bill/BillDailyList.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div v-loading="billStore.isLoading"
|
||||
element-loading-text="正在加载中..."
|
||||
class="bill-daily-list">
|
||||
<el-date-picker v-model="billStore.billQueryForm.billYear"
|
||||
type="year" :editable="false" style="width: 120px;"
|
||||
@change="billStore.refreshInfo()"/>
|
||||
|
||||
<el-empty v-if="billDailyInfo.dateList.length === 0"/>
|
||||
|
||||
<el-scrollbar>
|
||||
<bill-daily-item v-for="(item, index) in billDailyInfo.dateList"
|
||||
:key="index" :date="item"/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BillDailyItem from '@/components/Bill/BillDailyItem.vue'
|
||||
import { reactive, watch } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
|
||||
const billStore = useBillStore()
|
||||
|
||||
watch(() => billStore.isRefresh, () => {
|
||||
updateInfo()
|
||||
})
|
||||
|
||||
const billDailyInfo = reactive({
|
||||
dateList: [] as string[]
|
||||
})
|
||||
|
||||
const updateInfo = () => {
|
||||
const dateList = billStore.billRecordList.map((item) => item.date)
|
||||
billDailyInfo.dateList = [...new Set(dateList)] as string[]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.bill-daily-list {
|
||||
height: calc(100vh - 120px);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
|
||||
.el-date-editor {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
.el-scrollbar__view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
235
src/components/Bill/BillDialog.vue
Normal file
235
src/components/Bill/BillDialog.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<el-dialog v-model="billStore.billDialog.visible"
|
||||
title="账单内容" center class="bill-dialog"
|
||||
@open="openDialogEvent">
|
||||
<el-form ref="formRef" :model="billStore.billDialog.data"
|
||||
:rules="rules" label-width="80px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="账本类型" prop="bookName">
|
||||
<el-radio-group v-model="billStore.billDialog.data.bookName"
|
||||
@change="changeBillBookNameEvent">
|
||||
<el-radio v-for="(item, index) in billStore.billBookList"
|
||||
:value="item.name" :key="index">
|
||||
<template #default>
|
||||
<i :class="item.icon" :style="{color: item.color}"/>
|
||||
<span style="margin-left: 5px;">{{ item.name }}</span>
|
||||
</template>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单类别" prop="type">
|
||||
<el-radio-group v-model="billStore.billDialog.data.type"
|
||||
@change="changeBillCategoryEvent">
|
||||
<el-radio v-for="(item, index) in billInfo.billTypeList"
|
||||
:value="item" :key="index">
|
||||
{{ getBillTypeName(item) }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单分类" prop="category">
|
||||
<el-radio-group v-model="billStore.billDialog.data.category">
|
||||
<el-radio v-for="(item, index) in billInfo.billCategoryList"
|
||||
:value="item.name" :key="index">
|
||||
<template #default>
|
||||
<i :class="item.icon" :style="{color: item.color}"/>
|
||||
<span style="margin-left: 5px;">{{ item.name }}</span>
|
||||
</template>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单时间" prop="date">
|
||||
<el-date-picker
|
||||
v-model="billStore.billDialog.data.date"
|
||||
type="date" :editable="false" placeholder="请选择账单时间"
|
||||
value-format="YYYY-MM-DD"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单地点" prop="location">
|
||||
<el-input v-model="billStore.billDialog.data.location" autocomplete="off"
|
||||
placeholder="请输入账单地点" clearable
|
||||
show-word-limit maxlength="10">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><location/></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单账户" prop="payAccount">
|
||||
<el-radio-group v-model="billStore.billDialog.data.payAccount">
|
||||
<el-radio v-for="(item, index) in billStore.billPayList"
|
||||
:value="item.name" :key="index">
|
||||
<template #default>
|
||||
<i :class="item.icon" :style="{color: item.color}"/>
|
||||
<span style="margin-left: 5px;">{{ item.name }}</span>
|
||||
</template>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单金额" prop="amount">
|
||||
<amount-input v-model="billStore.billDialog.data.amount"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账单内容" prop="content">
|
||||
<el-input v-model="billStore.billDialog.data.content" autocomplete="off"
|
||||
placeholder="请输入账单内容" clearable
|
||||
show-word-limit maxlength="10">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><document/></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="账单备注">
|
||||
<el-input v-model="billStore.billDialog.data.remark" type="textarea"
|
||||
placeholder="请输入账单备注" clearable
|
||||
:rows="5" show-word-limit maxlength="200">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="账单图片">
|
||||
<upload-image v-model="billStore.billDialog.data.imageList"
|
||||
:service-url="fileServiceUrl"
|
||||
:service-file-root-path="serviceFileRootPath"
|
||||
:image-path="billStore.billImagePath"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelClick">取消</el-button>
|
||||
<el-button type="primary" @click="confirmClick">
|
||||
{{ billStore.billApiType === 'UPDATE' ? '编辑' : '新增' }}
|
||||
</el-button>
|
||||
<el-button v-if="billStore.billApiType === 'UPDATE'"
|
||||
type="danger" @click="deleteClick">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadImage, AmountInput } from 'vue3-common'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { Location, Document } from '@element-plus/icons-vue'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { billRecordRules } from '@/utils/element/elRules.ts'
|
||||
import { nextTick, reactive, ref } from 'vue'
|
||||
import { IBillCategory } from '@/types/bill.ts'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
||||
|
||||
const formRef = ref()
|
||||
const rules = reactive<FormRules>(billRecordRules)
|
||||
const billStore = useBillStore()
|
||||
|
||||
const billInfo = reactive({
|
||||
billTypeList: [] as string[],
|
||||
billCategoryList: [] as IBillCategory[]
|
||||
})
|
||||
|
||||
/**
|
||||
* 打开对话框事件
|
||||
*/
|
||||
const openDialogEvent = async () => {
|
||||
// 如果是新增账单 默认选择第一个账本
|
||||
if (billStore.billApiType !== 'UPDATE') {
|
||||
billStore.billDialog.data.bookName = billStore.billBookList[0].name
|
||||
}
|
||||
getBillTypeByBookName(billStore.billDialog.data.bookName)
|
||||
|
||||
// 如果是新增账单 默认选择一个类别
|
||||
if (billStore.billApiType !== 'UPDATE') {
|
||||
billStore.billDialog.data.type = billInfo.billTypeList[0]
|
||||
}
|
||||
getBillCategory()
|
||||
|
||||
// 如果是新增账单 默认选择一个分类
|
||||
if (billStore.billApiType !== 'UPDATE') {
|
||||
billStore.billDialog.data.category = billInfo.billCategoryList[0].name
|
||||
billStore.billDialog.data.payAccount = billStore.billPayList[0].name
|
||||
}
|
||||
|
||||
await nextTick(() => {
|
||||
formRef.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账单类型名称
|
||||
* @param type 账单类型
|
||||
*/
|
||||
const getBillTypeName = (type: string) => {
|
||||
return type === 'expensive' ? '支出' : '收入'
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择账本事件
|
||||
* @param value 账本名称
|
||||
*/
|
||||
const changeBillBookNameEvent = (value: string) => {
|
||||
getBillTypeByBookName(value)
|
||||
billStore.billDialog.data.type = billInfo.billTypeList[0]
|
||||
getBillCategory()
|
||||
billStore.billDialog.data.category = billInfo.billCategoryList[0].name
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择账单类别事件
|
||||
*/
|
||||
const changeBillCategoryEvent = () => {
|
||||
getBillCategory()
|
||||
billStore.billDialog.data.category = billInfo.billCategoryList[0].name
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过账本获取账单类型
|
||||
* @param bookName 账本
|
||||
*/
|
||||
const getBillTypeByBookName = (bookName: string) => {
|
||||
billInfo.billTypeList = []
|
||||
billStore.billCategoryList.forEach((item) => {
|
||||
if (item.bookName === bookName && !billInfo.billTypeList.includes(item.type)) {
|
||||
billInfo.billTypeList.push(item.type)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账单类别
|
||||
*/
|
||||
const getBillCategory = () => {
|
||||
billInfo.billCategoryList = billStore.billCategoryList.filter((value) => {
|
||||
return value.bookName === billStore.billDialog.data.bookName
|
||||
&& value.type === billStore.billDialog.data.type
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击确认按钮
|
||||
*/
|
||||
const confirmClick = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
billStore.handleBillApi(billStore.billDialog.data)
|
||||
})
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
billStore.billDialog.visible = false
|
||||
}
|
||||
|
||||
const deleteClick = () => {
|
||||
commonElMessageBox('是否确认删除该账单内容?').then(() => {
|
||||
billStore.billApiType = 'DELETE'
|
||||
billStore.handleBillApi(billStore.billDialog.data)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
166
src/components/Bill/BillPay.vue
Normal file
166
src/components/Bill/BillPay.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="bill-setting">
|
||||
<div class="button-container">
|
||||
<el-button type="primary" @click="addNewPayClick">新增账户</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="billStore.billPayList"
|
||||
border stripe
|
||||
:header-cell-style="tableHeaderStyle()"
|
||||
:cell-style="tableCellStyle()"
|
||||
:row-style="tableRowStyle()"
|
||||
style="width: 100%"
|
||||
class="card-item">
|
||||
<el-table-column type="index" align="center" width="50" />
|
||||
<el-table-column prop="name" label="名称" align="center" width="100"/>
|
||||
<el-table-column label="图标" align="center" width="80">
|
||||
<template #default="scope">
|
||||
<i :class="scope.row.icon" :style="{color: scope.row.color, fontSize: '24px'}"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" @click="editPayClick(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deletePayClick(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="billInfo.billPayDialog.visible"
|
||||
:title="billInfo.billPayDialog.title"
|
||||
center width="80%" @open="openDialogEvent">
|
||||
<el-form ref="formRef" :model="billInfo.billPayDialog.data"
|
||||
:rules="rules" label-position="top" label-width="100px">
|
||||
<el-form-item label="账户名称" prop="name">
|
||||
<el-input v-model="billInfo.billPayDialog.data.name" autocomplete="off"
|
||||
placeholder="请输入类型名称"
|
||||
:maxlength="10" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="账户图标" prop="icon">
|
||||
<el-input v-model="billInfo.billPayDialog.data.icon" autocomplete="off"
|
||||
placeholder="请输入类型图标"
|
||||
:maxlength="40" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="图标颜色" prop="color">
|
||||
<el-color-picker v-model="billInfo.billPayDialog.data.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标预览">
|
||||
<i :class="billInfo.billPayDialog.data.icon"
|
||||
:style="{color: billInfo.billPayDialog.data.color, fontSize: '24px'}"></i>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelClick">取消</el-button>
|
||||
<el-button type="primary" @click="confirmClick">确认</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { ElMessage, FormRules } from 'element-plus'
|
||||
import { IBillPay } from '@/types/bill.ts'
|
||||
import { billPayRules } from '@/utils/element/elRules.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { addBillPayApi, updateBillPayApi } from '@/apis/bill.ts'
|
||||
import { tableCellStyle, tableHeaderStyle, tableRowStyle } from 'vue3-common/utils/elUtil'
|
||||
import type { IDialog, IHandleApi } from 'vue3-common/types'
|
||||
|
||||
const formRef = ref()
|
||||
const rules = reactive<FormRules>(billPayRules)
|
||||
const billStore = useBillStore()
|
||||
|
||||
const billInfo = reactive({
|
||||
setBillPayType: 'ADD' as IHandleApi,
|
||||
billPayDialog: {
|
||||
title: '',
|
||||
visible: false,
|
||||
data: {}
|
||||
} as IDialog<IBillPay>
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await billStore.queryBillPay()
|
||||
})
|
||||
|
||||
/**
|
||||
* 点击新增账户按钮
|
||||
*/
|
||||
const addNewPayClick = () => {
|
||||
billInfo.setBillPayType = 'ADD'
|
||||
billInfo.billPayDialog = {
|
||||
title: '新增账户',
|
||||
visible: true,
|
||||
data: {
|
||||
name: '',
|
||||
icon: '',
|
||||
color: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击编辑账户按钮
|
||||
* @param pay 账户
|
||||
*/
|
||||
const editPayClick = async (pay: IBillPay) => {
|
||||
billInfo.setBillPayType = 'UPDATE'
|
||||
billInfo.billPayDialog = {
|
||||
title: '编辑账户',
|
||||
visible: true,
|
||||
data: deepCopyObject(pay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击删除账户按钮
|
||||
* @param pay 账户
|
||||
*/
|
||||
const deletePayClick = (pay: IBillPay) => {
|
||||
billInfo.setBillPayType = 'DELETE'
|
||||
ElMessage.error('暂不支持该功能')
|
||||
}
|
||||
|
||||
const openDialogEvent = () => {
|
||||
nextTick(() => {
|
||||
formRef.value.clearValidate()
|
||||
})
|
||||
}
|
||||
|
||||
const setBillBookApi = async (id?: number) => {
|
||||
switch (billInfo.setBillPayType) {
|
||||
case 'ADD':
|
||||
await addBillPayApi(billInfo.billPayDialog.data)
|
||||
ElMessage.success('新增账户成功')
|
||||
break
|
||||
case 'UPDATE':
|
||||
await updateBillPayApi(id as number, billInfo.billPayDialog.data)
|
||||
ElMessage.success('更新账户成功')
|
||||
break
|
||||
case 'DELETE':
|
||||
ElMessage.success('删除账户成功')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
await billStore.queryBillPay()
|
||||
billInfo.billPayDialog.visible = false
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
billInfo.billPayDialog.visible = false
|
||||
}
|
||||
|
||||
const confirmClick = async () => {
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
setBillBookApi(billInfo.billPayDialog.data.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
63
src/components/Bill/BillQuery.vue
Normal file
63
src/components/Bill/BillQuery.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="common-query">
|
||||
<el-select v-model="billStore.billQueryForm.billBook"
|
||||
@change="billStore.refreshInfo()" style="width: 120px">
|
||||
<el-option
|
||||
v-for="(item, index) in billQuery.billBookList"
|
||||
:key="index" :label="item.label" :value="item.name"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="billStore.billDateType"
|
||||
@change="billStore.refreshInfo()"
|
||||
style="width: 120px;align-self: center;">
|
||||
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
|
||||
:label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
|
||||
<el-switch
|
||||
v-model="billStore.billType"
|
||||
inline-prompt
|
||||
active-value="income" inactive-value="expensive"
|
||||
active-text="收入账单" inactive-text="支出账单"
|
||||
@change="billStore.refreshInfo()"
|
||||
style="--el-switch-on-color: #6FBB69; --el-switch-off-color: #CA6C65;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { IBillBook } from '@/types/bill.ts'
|
||||
|
||||
const billStore = useBillStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await billStore.initBillInfo()
|
||||
|
||||
billQuery.billBookList = deepCopyObject(billStore.billBookList)
|
||||
billQuery.billBookList.push({
|
||||
color: '',
|
||||
icon: '',
|
||||
id: 0,
|
||||
label: '全部账本',
|
||||
name: 'all'
|
||||
})
|
||||
})
|
||||
|
||||
const dateTypeOptions = [{
|
||||
label: '按月选择',
|
||||
value: 'month'
|
||||
},
|
||||
{
|
||||
label: '按年选择',
|
||||
value: 'year'
|
||||
}]
|
||||
|
||||
const billQuery = reactive({
|
||||
query: '',
|
||||
billBookList: [] as IBillBook[]
|
||||
})
|
||||
</script>
|
||||
22
src/components/Bill/BillRank.vue
Normal file
22
src/components/Bill/BillRank.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div v-for="(item, index) in billRankList.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">¥{{ item.amount }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { getBillSortByAmount } from '@/utils/home/billUtil.ts'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const billStore = useBillStore()
|
||||
|
||||
const MAX_RANK_COUNT = 10
|
||||
|
||||
const billRankList = computed(() => {
|
||||
return getBillSortByAmount(billStore.billRecordList, billStore.billType)
|
||||
})
|
||||
</script>
|
||||
45
src/components/Bill/BillStats.vue
Normal file
45
src/components/Bill/BillStats.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="common-statistics">
|
||||
<stats-card
|
||||
title="当前支出" color="#CA6C65" unit="元" icon="fa-solid fa-sack-xmark"
|
||||
:value="formatAmount(billStats.expensive)" />
|
||||
|
||||
<stats-card
|
||||
title="当前收入" color="#6FBB69" unit="元" icon="fa-solid fa-sack-dollar"
|
||||
:value="formatAmount(billStats.income)" />
|
||||
|
||||
<stats-card
|
||||
title="当前结余" color="#E4BF62" unit="元" icon="fa-solid fa-coins"
|
||||
:value="formatAmount(billStats.balance)" />
|
||||
|
||||
<stats-card
|
||||
title="支出占比" color="#5470C6" unit="%" icon="fa-solid fa-chart-pie"
|
||||
:value="(billStats.rate * 100).toFixed(2)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StatsCard from '@/components/StatsCard.vue'
|
||||
import { useBillStore } from '@/store/bill.ts'
|
||||
import { ref, watch } from 'vue'
|
||||
import { getBillSummaryStats } from '@/utils/home/billUtil.ts'
|
||||
import { IBillSummaryStats } from '@/types/bill.ts'
|
||||
import { formatAmount } from 'vue3-common/utils/numberUtil'
|
||||
|
||||
const billStore = useBillStore()
|
||||
|
||||
watch(() => billStore.isRefresh, () => {
|
||||
updateStats()
|
||||
})
|
||||
|
||||
const billStats = ref<IBillSummaryStats>({
|
||||
balance: 0,
|
||||
expensive: 0,
|
||||
income: 0,
|
||||
rate: 0
|
||||
})
|
||||
|
||||
const updateStats = () => {
|
||||
billStats.value = getBillSummaryStats(billStore.billRecordList)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user