封装调整
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 25s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 25s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
This commit is contained in:
@@ -43,22 +43,14 @@ public class BudgetRecord : BaseEntity
|
|||||||
|
|
||||||
public enum BudgetPeriodType
|
public enum BudgetPeriodType
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 周
|
|
||||||
/// </summary>
|
|
||||||
Week,
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 月
|
/// 月
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Month,
|
Month = 1,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 年
|
/// 年
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Year,
|
Year = 2
|
||||||
/// <summary>
|
|
||||||
/// 长期
|
|
||||||
/// </summary>
|
|
||||||
Longterm
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum BudgetCategory
|
public enum BudgetCategory
|
||||||
|
|||||||
@@ -61,19 +61,10 @@ public class BudgetService(
|
|||||||
|
|
||||||
public static (DateTime start, DateTime end) GetPeriodRange(DateTime startDate, BudgetPeriodType type, DateTime referenceDate)
|
public static (DateTime start, DateTime end) GetPeriodRange(DateTime startDate, BudgetPeriodType type, DateTime referenceDate)
|
||||||
{
|
{
|
||||||
if (type == BudgetPeriodType.Longterm) return (startDate, DateTime.MaxValue);
|
|
||||||
|
|
||||||
DateTime start;
|
DateTime start;
|
||||||
DateTime end;
|
DateTime end;
|
||||||
|
|
||||||
if (type == BudgetPeriodType.Week)
|
if (type == BudgetPeriodType.Month)
|
||||||
{
|
|
||||||
var daysFromStart = (referenceDate.Date - startDate.Date).Days;
|
|
||||||
var weeksFromStart = daysFromStart / 7;
|
|
||||||
start = startDate.Date.AddDays(weeksFromStart * 7);
|
|
||||||
end = start.AddDays(6).AddHours(23).AddMinutes(59).AddSeconds(59);
|
|
||||||
}
|
|
||||||
else if (type == BudgetPeriodType.Month)
|
|
||||||
{
|
{
|
||||||
start = new DateTime(referenceDate.Year, referenceDate.Month, 1);
|
start = new DateTime(referenceDate.Year, referenceDate.Month, 1);
|
||||||
end = start.AddMonths(1).AddDays(-1).AddHours(23).AddMinutes(59).AddSeconds(59);
|
end = start.AddMonths(1).AddDays(-1).AddHours(23).AddMinutes(59).AddSeconds(59);
|
||||||
|
|||||||
@@ -52,27 +52,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<!-- 分类按钮网格 -->
|
<!-- 分类选择组件 -->
|
||||||
<div class="classify-buttons">
|
<ClassifySelector
|
||||||
<van-button
|
v-model="categoryName"
|
||||||
type="success"
|
:type="form.type"
|
||||||
size="small"
|
/>
|
||||||
class="classify-btn"
|
|
||||||
@click="addClassifyDialogRef.open()"
|
|
||||||
>
|
|
||||||
+ 新增
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-for="item in categoryList"
|
|
||||||
:key="item.id"
|
|
||||||
:type="categoryName === item.name ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="selectClassify(item)"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
@@ -83,12 +67,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</van-form>
|
</van-form>
|
||||||
|
|
||||||
<!-- 新增分类对话框 -->
|
|
||||||
<AddClassifyDialog
|
|
||||||
ref="addClassifyDialogRef"
|
|
||||||
@confirm="handleAddClassify"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 日期选择弹窗 -->
|
<!-- 日期选择弹窗 -->
|
||||||
<van-popup v-model:show="showDatePicker" position="bottom" round teleport="body">
|
<van-popup v-model:show="showDatePicker" position="bottom" round teleport="body">
|
||||||
<van-date-picker
|
<van-date-picker
|
||||||
@@ -115,8 +93,7 @@
|
|||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, onMounted, watch } from 'vue'
|
||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import AddClassifyDialog from '@/components/AddClassifyDialog.vue'
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
initialData: {
|
initialData: {
|
||||||
@@ -135,13 +112,10 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['submit'])
|
const emit = defineEmits(['submit'])
|
||||||
|
|
||||||
const addClassifyDialogRef = ref()
|
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = ref({
|
const form = ref({
|
||||||
type: 0, // 0: 支出, 1: 收入, 2: 不计
|
type: 0, // 0: 支出, 1: 收入, 2: 不计
|
||||||
amount: '',
|
amount: '',
|
||||||
categoryId: null,
|
|
||||||
date: dayjs().format('YYYY-MM-DD'),
|
date: dayjs().format('YYYY-MM-DD'),
|
||||||
time: dayjs().format('HH:mm'),
|
time: dayjs().format('HH:mm'),
|
||||||
note: ''
|
note: ''
|
||||||
@@ -153,10 +127,7 @@ const categoryName = ref('')
|
|||||||
const showDatePicker = ref(false)
|
const showDatePicker = ref(false)
|
||||||
const showTimePicker = ref(false)
|
const showTimePicker = ref(false)
|
||||||
|
|
||||||
// 选择器数据
|
// 日期时间临时变量 (Vant DatePicker 需要数组 or 特定格式)
|
||||||
const categoryList = ref([])
|
|
||||||
|
|
||||||
// 日期时间临时变量 (Vant DatePicker 需要数组或特定格式)
|
|
||||||
const currentDate = ref(dayjs().format('YYYY-MM-DD').split('-'))
|
const currentDate = ref(dayjs().format('YYYY-MM-DD').split('-'))
|
||||||
const currentTime = ref(dayjs().format('HH:mm').split(':'))
|
const currentTime = ref(dayjs().format('HH:mm').split(':'))
|
||||||
|
|
||||||
@@ -177,29 +148,11 @@ const initForm = async () => {
|
|||||||
if (reason !== undefined) form.value.note = reason
|
if (reason !== undefined) form.value.note = reason
|
||||||
if (type !== undefined) form.value.type = type
|
if (type !== undefined) form.value.type = type
|
||||||
|
|
||||||
// 加载分类列表
|
// 如果有传入分类名称,尝试设置
|
||||||
await loadClassifyList(form.value.type)
|
|
||||||
|
|
||||||
// 如果有传入分类名称,尝试匹配
|
|
||||||
if (classify) {
|
if (classify) {
|
||||||
const found = categoryList.value.find(c => c.name === classify)
|
|
||||||
if (found) {
|
|
||||||
selectClassify(found)
|
|
||||||
} else {
|
|
||||||
// 如果没找到对应分类,但有分类名称,可能需要特殊处理或者就显示名称但不关联ID?
|
|
||||||
// 这里暂时只显示名称,ID为空,或者需要自动创建?
|
|
||||||
// 按照原有逻辑,后端需要分类名称,所以这里只要设置 categoryName 即可
|
|
||||||
// 但是 ManualBillAdd 原逻辑是需要 categoryId 的。
|
|
||||||
// 不过 createTransaction 接口传的是 classify (name)。
|
|
||||||
// 让我们看 ManualBillAdd 的 handleSave:
|
|
||||||
// classify: categoryName.value
|
|
||||||
// 所以只要 categoryName 有值就行。
|
|
||||||
categoryName.value = classify
|
categoryName.value = classify
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
await loadClassifyList(form.value.type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -213,50 +166,6 @@ watch(() => props.initialData, () => {
|
|||||||
|
|
||||||
const handleTypeChange = (newType) => {
|
const handleTypeChange = (newType) => {
|
||||||
categoryName.value = ''
|
categoryName.value = ''
|
||||||
form.value.categoryId = null
|
|
||||||
loadClassifyList(newType)
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadClassifyList = async (type = null) => {
|
|
||||||
try {
|
|
||||||
const response = await getCategoryList(type)
|
|
||||||
if (response.success) {
|
|
||||||
categoryList.value = response.data || []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载分类列表出错:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectClassify = (item) => {
|
|
||||||
categoryName.value = item.name
|
|
||||||
form.value.categoryId = item.id
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddClassify = async (name) => {
|
|
||||||
try {
|
|
||||||
// 调用API创建分类
|
|
||||||
const response = await createCategory({
|
|
||||||
name: name,
|
|
||||||
type: form.value.type
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
showToast('分类创建成功')
|
|
||||||
const newId = response.data
|
|
||||||
// 重新加载分类列表
|
|
||||||
await loadClassifyList(form.value.type)
|
|
||||||
|
|
||||||
// 选中新创建的分类
|
|
||||||
categoryName.value = name
|
|
||||||
form.value.categoryId = newId
|
|
||||||
} else {
|
|
||||||
showToast(response.message || '创建分类失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建分类出错:', error)
|
|
||||||
showToast('创建分类失败')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onConfirmDate = ({ selectedValues }) => {
|
const onConfirmDate = ({ selectedValues }) => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<span class="percent" :class="percentClass">{{ percentage }}%</span>
|
<span class="percent" :class="percentClass">{{ percentage }}%</span>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="budget.type !== BudgetPeriodType.Longterm" class="progress-section time-progress">
|
<div class="progress-section time-progress">
|
||||||
<span class="period-type">时间进度</span>
|
<span class="period-type">时间进度</span>
|
||||||
<van-progress
|
<van-progress
|
||||||
:percentage="timePercentage"
|
:percentage="timePercentage"
|
||||||
@@ -126,7 +126,7 @@ const percentage = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const timePercentage = computed(() => {
|
const timePercentage = computed(() => {
|
||||||
if (!props.budget.periodStart || !props.budget.periodEnd || props.budget.type === BudgetPeriodType.Longterm) return 0
|
if (!props.budget.periodStart || !props.budget.periodEnd) return 0
|
||||||
const start = new Date(props.budget.periodStart).getTime()
|
const start = new Date(props.budget.periodStart).getTime()
|
||||||
const end = new Date(props.budget.periodEnd).getTime()
|
const end = new Date(props.budget.periodEnd).getTime()
|
||||||
const now = new Date().getTime()
|
const now = new Date().getTime()
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
<van-field name="type" label="统计周期">
|
<van-field name="type" label="统计周期">
|
||||||
<template #input>
|
<template #input>
|
||||||
<van-radio-group v-model="form.type" direction="horizontal">
|
<van-radio-group v-model="form.type" direction="horizontal">
|
||||||
<van-radio :name="BudgetPeriodType.Week">周</van-radio>
|
|
||||||
<van-radio :name="BudgetPeriodType.Month">月</van-radio>
|
<van-radio :name="BudgetPeriodType.Month">月</van-radio>
|
||||||
<van-radio :name="BudgetPeriodType.Year">年</van-radio>
|
<van-radio :name="BudgetPeriodType.Year">年</van-radio>
|
||||||
</van-radio-group>
|
</van-radio-group>
|
||||||
@@ -45,29 +44,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
<div class="classify-buttons">
|
<ClassifySelector
|
||||||
<van-button
|
v-model="form.selectedCategories"
|
||||||
v-if="filteredCategories.length > 0"
|
:type="budgetType"
|
||||||
:type="isAllSelected ? 'primary' : 'default'"
|
multiple
|
||||||
size="small"
|
:show-add="false"
|
||||||
class="classify-btn all-btn"
|
:show-clear="false"
|
||||||
@click="toggleAll"
|
/>
|
||||||
>
|
|
||||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-for="item in filteredCategories"
|
|
||||||
:key="item.id"
|
|
||||||
:type="form.selectedCategories.includes(item.name) ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="toggleCategory(item.name)"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</van-button>
|
|
||||||
<div v-if="filteredCategories.length === 0" class="no-data">暂无分类</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</van-form>
|
</van-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,19 +61,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
import { getCategoryList } from '@/api/transactionCategory'
|
|
||||||
import { createBudget, updateBudget } from '@/api/budget'
|
import { createBudget, updateBudget } from '@/api/budget'
|
||||||
import { BudgetPeriodType, BudgetCategory } from '@/constants/enums'
|
import { BudgetPeriodType, BudgetCategory } from '@/constants/enums'
|
||||||
import PopupContainer from '@/components/PopupContainer.vue'
|
import PopupContainer from '@/components/PopupContainer.vue'
|
||||||
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
|
|
||||||
const categories = ref([])
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: '',
|
name: '',
|
||||||
@@ -137,44 +119,10 @@ defineExpose({
|
|||||||
open
|
open
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredCategories = computed(() => {
|
const budgetType = computed(() => {
|
||||||
const targetType = form.category === BudgetCategory.Expense ? 0 : (form.category === BudgetCategory.Income ? 1 : 2)
|
return form.category === BudgetCategory.Expense ? 0 : (form.category === BudgetCategory.Income ? 1 : 2)
|
||||||
return categories.value.filter(c => c.type === targetType)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isAllSelected = computed(() => {
|
|
||||||
return filteredCategories.value.length > 0 &&
|
|
||||||
filteredCategories.value.every(c => form.selectedCategories.includes(c.name))
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleCategory = (name) => {
|
|
||||||
const index = form.selectedCategories.indexOf(name)
|
|
||||||
if (index > -1) {
|
|
||||||
form.selectedCategories.splice(index, 1)
|
|
||||||
} else {
|
|
||||||
form.selectedCategories.push(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleAll = () => {
|
|
||||||
if (isAllSelected.value) {
|
|
||||||
form.selectedCategories = []
|
|
||||||
} else {
|
|
||||||
form.selectedCategories = filteredCategories.value.map(c => c.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchCategories = async () => {
|
|
||||||
try {
|
|
||||||
const res = await getCategoryList()
|
|
||||||
if (res.success) {
|
|
||||||
categories.value = res.data || []
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('获取分类列表失败', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -207,10 +155,6 @@ const getCategoryName = (category) => {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchCategories()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -235,29 +179,9 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classify-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classify-btn {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
min-width: 60px;
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.all-btn {
|
|
||||||
font-weight: bold;
|
|
||||||
border-style: dashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data {
|
.no-data {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #969799;
|
color: #969799;
|
||||||
padding: 8px 0;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const periodConfigs = {
|
const periodConfigs = {
|
||||||
week: { label: '本周', showDivider: true },
|
|
||||||
month: { label: '本月', showDivider: true },
|
month: { label: '本月', showDivider: true },
|
||||||
year: { label: '年度', showDivider: false }
|
year: { label: '年度', showDivider: false }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,29 +11,13 @@
|
|||||||
|
|
||||||
<div class="category-section">
|
<div class="category-section">
|
||||||
<div class="section-title">可多选分类</div>
|
<div class="section-title">可多选分类</div>
|
||||||
<div class="classify-buttons">
|
<ClassifySelector
|
||||||
<van-button
|
v-model="selectedCategories"
|
||||||
v-if="incomeCategories.length > 0"
|
:type="2"
|
||||||
:type="isAllSelected ? 'primary' : 'default'"
|
multiple
|
||||||
size="small"
|
:show-add="false"
|
||||||
class="classify-btn all-btn"
|
:show-clear="false"
|
||||||
@click="toggleAll"
|
/>
|
||||||
>
|
|
||||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-for="item in incomeCategories"
|
|
||||||
:key="item.id"
|
|
||||||
:type="selectedCategories.includes(item.name) ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
style="margin-bottom: 8px; margin-right: 8px;"
|
|
||||||
@click="toggleCategory(item.name)"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</van-button>
|
|
||||||
<div v-if="incomeCategories.length === 0" class="no-data">暂无收入分类</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,21 +28,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { showToast, showLoadingToast, closeToast } from 'vant'
|
import { showToast, showLoadingToast, closeToast } from 'vant'
|
||||||
import { getCategoryList } from '@/api/transactionCategory'
|
|
||||||
import { getConfig, setConfig } from '@/api/config'
|
import { getConfig, setConfig } from '@/api/config'
|
||||||
import PopupContainer from '@/components/PopupContainer.vue'
|
import PopupContainer from '@/components/PopupContainer.vue'
|
||||||
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const categories = ref([])
|
|
||||||
const selectedCategories = ref([])
|
const selectedCategories = ref([])
|
||||||
|
|
||||||
const open = async () => {
|
const open = async () => {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
await fetchCategories()
|
|
||||||
await fetchConfig()
|
await fetchConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,43 +48,6 @@ defineExpose({
|
|||||||
open
|
open
|
||||||
})
|
})
|
||||||
|
|
||||||
const incomeCategories = computed(() => {
|
|
||||||
return categories.value.filter(c => c.type === 1) // Income = 1
|
|
||||||
})
|
|
||||||
|
|
||||||
const isAllSelected = computed(() => {
|
|
||||||
return incomeCategories.value.length > 0 &&
|
|
||||||
incomeCategories.value.every(c => selectedCategories.value.includes(c.name))
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleCategory = (name) => {
|
|
||||||
const index = selectedCategories.value.indexOf(name)
|
|
||||||
if (index > -1) {
|
|
||||||
selectedCategories.value.splice(index, 1)
|
|
||||||
} else {
|
|
||||||
selectedCategories.value.push(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleAll = () => {
|
|
||||||
if (isAllSelected.value) {
|
|
||||||
selectedCategories.value = []
|
|
||||||
} else {
|
|
||||||
selectedCategories.value = incomeCategories.value.map(c => c.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchCategories = async () => {
|
|
||||||
try {
|
|
||||||
const res = await getCategoryList()
|
|
||||||
if (res.success) {
|
|
||||||
categories.value = res.data || []
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('获取分类列表失败', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchConfig = async () => {
|
const fetchConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getConfig('SavingsCategories')
|
const res = await getConfig('SavingsCategories')
|
||||||
@@ -156,22 +101,6 @@ const onSubmit = async () => {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classify-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classify-btn {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
margin-right: 8px;
|
|
||||||
border-radius: 20px;
|
|
||||||
min-width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.all-btn {
|
|
||||||
border-style: dashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data {
|
.no-data {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #969799;
|
color: #969799;
|
||||||
|
|||||||
249
Web/src/components/ClassifySelector.vue
Normal file
249
Web/src/components/ClassifySelector.vue
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<template>
|
||||||
|
<div class="classify-selector">
|
||||||
|
<div class="classify-buttons">
|
||||||
|
<!-- 全选按钮 (仅多选模式) -->
|
||||||
|
<van-button
|
||||||
|
v-if="multiple && showAll"
|
||||||
|
:type="isAllSelected ? 'primary' : 'default'"
|
||||||
|
size="small"
|
||||||
|
class="classify-btn all-btn"
|
||||||
|
@click="toggleAll"
|
||||||
|
>
|
||||||
|
{{ isAllSelected ? '取消全选' : '全选' }}
|
||||||
|
</van-button>
|
||||||
|
|
||||||
|
<!-- 新增按钮 -->
|
||||||
|
<van-button
|
||||||
|
v-if="showAdd"
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
class="classify-btn"
|
||||||
|
@click="openAddDialog"
|
||||||
|
>
|
||||||
|
+ 新增
|
||||||
|
</van-button>
|
||||||
|
|
||||||
|
<!-- 分类项按钮 -->
|
||||||
|
<van-button
|
||||||
|
v-for="item in displayOptions"
|
||||||
|
:key="item.id || item.text"
|
||||||
|
:type="isSelected(item) ? 'primary' : 'default'"
|
||||||
|
size="small"
|
||||||
|
class="classify-btn"
|
||||||
|
@click="toggleItem(item)"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</van-button>
|
||||||
|
|
||||||
|
<!-- 清空按钮 -->
|
||||||
|
<van-button
|
||||||
|
v-if="showClear && hasSelection"
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
class="classify-btn"
|
||||||
|
@click="clear"
|
||||||
|
>
|
||||||
|
清空
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增分类对话框 -->
|
||||||
|
<AddClassifyDialog
|
||||||
|
ref="addClassifyDialogRef"
|
||||||
|
@confirm="handleAddConfirm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
import { showToast } from 'vant'
|
||||||
|
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
||||||
|
import AddClassifyDialog from './AddClassifyDialog.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showAdd: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showClear: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'add', 'change'])
|
||||||
|
|
||||||
|
const innerOptions = ref([])
|
||||||
|
const addClassifyDialogRef = ref()
|
||||||
|
|
||||||
|
const displayOptions = computed(() => {
|
||||||
|
if (props.options) return props.options
|
||||||
|
return innerOptions.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchOptions = async () => {
|
||||||
|
if (props.options) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getCategoryList(props.type)
|
||||||
|
if (response.success) {
|
||||||
|
innerOptions.value = (response.data || []).map(item => ({
|
||||||
|
text: item.name,
|
||||||
|
value: item.name,
|
||||||
|
id: item.id
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('ClassifySelector 加载分类失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开新增对话框
|
||||||
|
const openAddDialog = () => {
|
||||||
|
addClassifyDialogRef.value?.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理新增确认
|
||||||
|
const handleAddConfirm = async (categoryName) => {
|
||||||
|
try {
|
||||||
|
// 调用API创建分类
|
||||||
|
const response = await createCategory({
|
||||||
|
name: categoryName,
|
||||||
|
type: props.type
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
showToast('分类创建成功')
|
||||||
|
// 刷新列表
|
||||||
|
await fetchOptions()
|
||||||
|
|
||||||
|
// 如果是单选模式,且当前没有选值或就是为了新增,则自动选中
|
||||||
|
if (!props.multiple) {
|
||||||
|
emit('update:modelValue', categoryName)
|
||||||
|
emit('change', categoryName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(response.message || '创建分类失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('ClassifySelector 创建分类出错:', error)
|
||||||
|
showToast('创建分类失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.type, () => {
|
||||||
|
fetchOptions()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchOptions()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 公开刷新方法
|
||||||
|
defineExpose({
|
||||||
|
refresh: fetchOptions
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否选中
|
||||||
|
const isSelected = (item) => {
|
||||||
|
if (props.multiple) {
|
||||||
|
return Array.isArray(props.modelValue) && props.modelValue.includes(item.text)
|
||||||
|
}
|
||||||
|
return props.modelValue === item.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否全部选中
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
if (!props.multiple || displayOptions.value.length === 0) return false
|
||||||
|
return displayOptions.value.every(item => props.modelValue.includes(item.text))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否有任何选中
|
||||||
|
const hasSelection = computed(() => {
|
||||||
|
if (props.multiple) {
|
||||||
|
return Array.isArray(props.modelValue) && props.modelValue.length > 0
|
||||||
|
}
|
||||||
|
return !!props.modelValue
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换选中状态
|
||||||
|
const toggleItem = (item) => {
|
||||||
|
if (props.multiple) {
|
||||||
|
const newValue = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||||
|
const index = newValue.indexOf(item.text)
|
||||||
|
if (index > -1) {
|
||||||
|
newValue.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
newValue.push(item.text)
|
||||||
|
}
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
emit('change', newValue)
|
||||||
|
} else {
|
||||||
|
const newValue = props.modelValue === item.text ? '' : item.text
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
emit('change', newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换全选
|
||||||
|
const toggleAll = () => {
|
||||||
|
if (!props.multiple) return
|
||||||
|
|
||||||
|
if (isAllSelected.value) {
|
||||||
|
emit('update:modelValue', [])
|
||||||
|
emit('change', [])
|
||||||
|
} else {
|
||||||
|
const allValues = displayOptions.value.map(item => item.text)
|
||||||
|
emit('update:modelValue', allValues)
|
||||||
|
emit('change', allValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空
|
||||||
|
const clear = () => {
|
||||||
|
const newValue = props.multiple ? [] : ''
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
emit('change', newValue)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.classify-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.classify-btn {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 70px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all-btn {
|
||||||
|
font-weight: bold;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -129,36 +129,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<!-- 分类按钮网格 -->
|
<!-- 分类选择组件 -->
|
||||||
<div class="classify-buttons">
|
<ClassifySelector
|
||||||
<van-button
|
v-model="batchForm.classify"
|
||||||
type="success"
|
:type="batchForm.type"
|
||||||
size="small"
|
/>
|
||||||
class="classify-btn"
|
|
||||||
@click="addClassifyDialogRef.open()"
|
|
||||||
>
|
|
||||||
+ 新增
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-for="item in classifyOptions"
|
|
||||||
:key="item.id"
|
|
||||||
:type="batchForm.classify === item.text ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="selectClassify(item.text)"
|
|
||||||
>
|
|
||||||
{{ item.text }}
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-if="batchForm.classify"
|
|
||||||
type="warning"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="clearClassify"
|
|
||||||
>
|
|
||||||
清空
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</van-form>
|
</van-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -172,12 +147,6 @@
|
|||||||
</van-button>
|
</van-button>
|
||||||
</template>
|
</template>
|
||||||
</PopupContainer>
|
</PopupContainer>
|
||||||
|
|
||||||
<!-- 新增分类对话框 -->
|
|
||||||
<AddClassifyDialog
|
|
||||||
ref="addClassifyDialogRef"
|
|
||||||
@confirm="handleAddClassify"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -191,8 +160,7 @@ import {
|
|||||||
showConfirmDialog
|
showConfirmDialog
|
||||||
} from 'vant'
|
} from 'vant'
|
||||||
import { getReasonGroups, batchUpdateByReason, getTransactionList } from '@/api/transactionRecord'
|
import { getReasonGroups, batchUpdateByReason, getTransactionList } from '@/api/transactionRecord'
|
||||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
import ClassifySelector from './ClassifySelector.vue'
|
||||||
import AddClassifyDialog from './AddClassifyDialog.vue'
|
|
||||||
import TransactionList from './TransactionList.vue'
|
import TransactionList from './TransactionList.vue'
|
||||||
import TransactionDetail from './TransactionDetail.vue'
|
import TransactionDetail from './TransactionDetail.vue'
|
||||||
import PopupContainer from './PopupContainer.vue'
|
import PopupContainer from './PopupContainer.vue'
|
||||||
@@ -219,8 +187,6 @@ const selectedReasons = ref(new Set())
|
|||||||
const pageIndex = ref(1)
|
const pageIndex = ref(1)
|
||||||
const finished = ref(false)
|
const finished = ref(false)
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const categories = ref([])
|
|
||||||
|
|
||||||
// 弹窗状态
|
// 弹窗状态
|
||||||
const showTransactionList = ref(false)
|
const showTransactionList = ref(false)
|
||||||
const showTransactionDetail = ref(false)
|
const showTransactionDetail = ref(false)
|
||||||
@@ -239,27 +205,15 @@ const transactionPageSize = ref(20)
|
|||||||
const showBatchDialog = ref(false)
|
const showBatchDialog = ref(false)
|
||||||
const batchFormRef = ref(null)
|
const batchFormRef = ref(null)
|
||||||
const batchGroup = ref(null)
|
const batchGroup = ref(null)
|
||||||
const addClassifyDialogRef = ref()
|
|
||||||
const batchForm = ref({
|
const batchForm = ref({
|
||||||
type: null,
|
type: null,
|
||||||
typeName: '',
|
typeName: '',
|
||||||
classify: ''
|
classify: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 根据选中的类型过滤分类选项
|
|
||||||
const classifyOptions = computed(() => {
|
|
||||||
if (batchForm.value.type === null) return []
|
|
||||||
return categories.value
|
|
||||||
.filter(c => c.type === batchForm.value.type)
|
|
||||||
.map(c => ({ text: c.name, value: c.name, id: c.id }))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听交易类型变化,重新加载分类
|
// 监听交易类型变化,重新加载分类
|
||||||
watch(() => batchForm.value.type, (newVal) => {
|
watch(() => batchForm.value.type, (newVal) => {
|
||||||
batchForm.value.classify = ''
|
batchForm.value.classify = ''
|
||||||
if (newVal !== null) {
|
|
||||||
loadCategories(newVal)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取类型名称
|
// 获取类型名称
|
||||||
@@ -361,62 +315,9 @@ const handleBatchClassify = (group) => {
|
|||||||
typeName: getTypeName(group.sampleType),
|
typeName: getTypeName(group.sampleType),
|
||||||
classify: group.sampleClassify || ''
|
classify: group.sampleClassify || ''
|
||||||
}
|
}
|
||||||
// 加载对应类型的分类列表
|
|
||||||
loadCategories(group.sampleType)
|
|
||||||
showBatchDialog.value = true
|
showBatchDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有分类
|
|
||||||
const loadCategories = async (type = null) => {
|
|
||||||
try {
|
|
||||||
const res = await getCategoryList(type)
|
|
||||||
if (res.success) {
|
|
||||||
categories.value = res.data || []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取分类列表失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择分类
|
|
||||||
const selectClassify = (classify) => {
|
|
||||||
batchForm.value.classify = classify
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增分类
|
|
||||||
const handleAddClassify = async (categoryName) => {
|
|
||||||
if (batchForm.value.type === null) {
|
|
||||||
showToast('请先选择交易类型')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 调用API创建分类
|
|
||||||
const response = await createCategory({
|
|
||||||
name: categoryName,
|
|
||||||
type: batchForm.value.type
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
showToast('分类创建成功')
|
|
||||||
// 重新加载分类列表
|
|
||||||
await loadCategories(batchForm.value.type)
|
|
||||||
batchForm.value.classify = categoryName
|
|
||||||
} else {
|
|
||||||
showToast(response.message || '创建分类失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建分类出错:', error)
|
|
||||||
showToast('创建分类失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空分类
|
|
||||||
const clearClassify = () => {
|
|
||||||
batchForm.value.classify = ''
|
|
||||||
showToast('已清空分类')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认批量更新
|
// 确认批量更新
|
||||||
const handleConfirmBatchUpdate = async () => {
|
const handleConfirmBatchUpdate = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -744,20 +645,5 @@ defineExpose({
|
|||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classify-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classify-btn {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
min-width: 70px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 交易列表弹窗 - 自定义样式 */
|
/* 交易列表弹窗 - 自定义样式 */
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -61,36 +61,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<!-- 分类按钮网格 -->
|
<!-- 分类选择组件 -->
|
||||||
<div class="classify-buttons">
|
<ClassifySelector
|
||||||
<van-button
|
v-model="editForm.classify"
|
||||||
type="success"
|
:type="editForm.type"
|
||||||
size="small"
|
@change="handleClassifyChange"
|
||||||
class="classify-btn"
|
/>
|
||||||
@click="addClassifyDialogRef.open()"
|
|
||||||
>
|
|
||||||
+ 新增
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-for="item in classifyColumns"
|
|
||||||
:key="item.id"
|
|
||||||
:type="editForm.classify === item.text ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="selectClassify(item.text)"
|
|
||||||
>
|
|
||||||
{{ item.text }}
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-if="editForm.classify"
|
|
||||||
type="warning"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="clearClassify"
|
|
||||||
>
|
|
||||||
清空
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</van-form>
|
</van-form>
|
||||||
|
|
||||||
@@ -107,12 +83,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</PopupContainer>
|
</PopupContainer>
|
||||||
|
|
||||||
<!-- 新增分类对话框 -->
|
|
||||||
<AddClassifyDialog
|
|
||||||
ref="addClassifyDialogRef"
|
|
||||||
@confirm="handleAddClassify"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 抵账候选列表弹窗 -->
|
<!-- 抵账候选列表弹窗 -->
|
||||||
<PopupContainer
|
<PopupContainer
|
||||||
v-model="showOffsetPopup"
|
v-model="showOffsetPopup"
|
||||||
@@ -138,9 +108,8 @@
|
|||||||
import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
|
import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
|
||||||
import { showToast, showConfirmDialog } from 'vant'
|
import { showToast, showConfirmDialog } from 'vant'
|
||||||
import PopupContainer from '@/components/PopupContainer.vue'
|
import PopupContainer from '@/components/PopupContainer.vue'
|
||||||
import AddClassifyDialog from '@/components/AddClassifyDialog.vue'
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||||
import { updateTransaction, getCandidatesForOffset, offsetTransactions } from '@/api/transactionRecord'
|
import { updateTransaction, getCandidatesForOffset, offsetTransactions } from '@/api/transactionRecord'
|
||||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
@@ -157,10 +126,6 @@ const emit = defineEmits(['update:show', 'save'])
|
|||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const addClassifyDialogRef = ref()
|
|
||||||
|
|
||||||
// 分类相关
|
|
||||||
const classifyColumns = ref([])
|
|
||||||
|
|
||||||
// 编辑表单
|
// 编辑表单
|
||||||
const editForm = reactive({
|
const editForm = reactive({
|
||||||
@@ -186,9 +151,6 @@ watch(() => props.transaction, (newVal) => {
|
|||||||
editForm.balance = String(newVal.balance)
|
editForm.balance = String(newVal.balance)
|
||||||
editForm.type = newVal.type
|
editForm.type = newVal.type
|
||||||
editForm.classify = newVal.classify || ''
|
editForm.classify = newVal.classify || ''
|
||||||
|
|
||||||
// 根据交易类型加载分类
|
|
||||||
loadClassifyList(newVal.type)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -200,26 +162,8 @@ watch(visible, (newVal) => {
|
|||||||
watch(() => editForm.type, (newVal) => {
|
watch(() => editForm.type, (newVal) => {
|
||||||
// 清空已选的分类
|
// 清空已选的分类
|
||||||
editForm.classify = ''
|
editForm.classify = ''
|
||||||
// 重新加载对应类型的分类列表
|
|
||||||
loadClassifyList(newVal)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载分类列表
|
|
||||||
const loadClassifyList = async (type = null) => {
|
|
||||||
try {
|
|
||||||
const response = await getCategoryList(type)
|
|
||||||
if (response.success) {
|
|
||||||
classifyColumns.value = (response.data || []).map(item => ({
|
|
||||||
text: item.name,
|
|
||||||
value: item.name,
|
|
||||||
id: item.id
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载分类列表出错:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交编辑
|
// 提交编辑
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -239,8 +183,6 @@ const onSubmit = async () => {
|
|||||||
showToast('保存成功')
|
showToast('保存成功')
|
||||||
visible.value = false
|
visible.value = false
|
||||||
emit('save', data)
|
emit('save', data)
|
||||||
// 重新加载分类列表
|
|
||||||
await loadClassifyList(editForm.type)
|
|
||||||
} else {
|
} else {
|
||||||
showToast(response.message || '保存失败')
|
showToast(response.message || '保存失败')
|
||||||
}
|
}
|
||||||
@@ -252,47 +194,15 @@ const onSubmit = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择分类
|
// 分类选择变化
|
||||||
const selectClassify = (classify) => {
|
const handleClassifyChange = () => {
|
||||||
editForm.classify = classify
|
|
||||||
|
|
||||||
if (editForm.id > 0 && editForm.type >= 0) {
|
if (editForm.id > 0 && editForm.type >= 0) {
|
||||||
// 直接保存
|
// 直接保存
|
||||||
onSubmit()
|
onSubmit()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增分类
|
|
||||||
const handleAddClassify = async (categoryName) => {
|
|
||||||
try {
|
|
||||||
// 调用API创建分类
|
|
||||||
const response = await createCategory({
|
|
||||||
name: categoryName,
|
|
||||||
type: editForm.type
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
showToast('分类创建成功')
|
|
||||||
// 重新加载分类列表
|
|
||||||
await loadClassifyList(editForm.type)
|
|
||||||
editForm.classify = categoryName
|
|
||||||
} else {
|
|
||||||
showToast(response.message || '创建分类失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建分类出错:', error)
|
|
||||||
showToast('创建分类失败')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空分类
|
// 清空分类
|
||||||
const clearClassify = () => {
|
|
||||||
editForm.classify = ''
|
|
||||||
showToast('已清空分类')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化日期
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
if (!dateString) return ''
|
if (!dateString) return ''
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
@@ -352,16 +262,4 @@ const handleCandidateSelect = (candidate) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.classify-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classify-btn {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
min-width: 70px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
* 预算周期类型
|
* 预算周期类型
|
||||||
*/
|
*/
|
||||||
export const BudgetPeriodType = {
|
export const BudgetPeriodType = {
|
||||||
Week: 0,
|
|
||||||
Month: 1,
|
Month: 1,
|
||||||
Year: 2,
|
Year: 2
|
||||||
Longterm: 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,15 +17,16 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-nav-bar>
|
</van-nav-bar>
|
||||||
|
|
||||||
<div class="scroll-content">
|
|
||||||
<div class="page-content">
|
|
||||||
<van-tabs v-model:active="activeTab" sticky offset-top="0" type="card">
|
|
||||||
<van-tab title="支出" :name="BudgetCategory.Expense">
|
|
||||||
<BudgetSummary
|
<BudgetSummary
|
||||||
|
v-if="activeTab !== BudgetCategory.Savings"
|
||||||
:stats="overallStats"
|
:stats="overallStats"
|
||||||
:title="activeTabTitle"
|
:title="activeTabTitle"
|
||||||
:get-value-class="getValueClass"
|
:get-value-class="getValueClass"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<van-tabs v-model:active="activeTab" type="card" class="budget-tabs">
|
||||||
|
<van-tab title="支出" :name="BudgetCategory.Expense">
|
||||||
|
<div class="scroll-content">
|
||||||
<div class="budget-list">
|
<div class="budget-list">
|
||||||
<template v-if="expenseBudgets?.length > 0">
|
<template v-if="expenseBudgets?.length > 0">
|
||||||
<van-swipe-cell v-for="budget in expenseBudgets" :key="budget.id">
|
<van-swipe-cell v-for="budget in expenseBudgets" :key="budget.id">
|
||||||
@@ -66,14 +67,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<van-empty v-else description="暂无支出预算" />
|
<van-empty v-else description="暂无支出预算" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 底部安全距离 -->
|
||||||
|
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
||||||
|
</div>
|
||||||
</van-tab>
|
</van-tab>
|
||||||
|
|
||||||
<van-tab title="收入" :name="BudgetCategory.Income">
|
<van-tab title="收入" :name="BudgetCategory.Income">
|
||||||
<BudgetSummary
|
<div class="scroll-content">
|
||||||
:stats="overallStats"
|
|
||||||
:title="activeTabTitle"
|
|
||||||
:get-value-class="getValueClass"
|
|
||||||
/>
|
|
||||||
<div class="budget-list">
|
<div class="budget-list">
|
||||||
<template v-if="incomeBudgets?.length > 0">
|
<template v-if="incomeBudgets?.length > 0">
|
||||||
<van-swipe-cell v-for="budget in incomeBudgets" :key="budget.id">
|
<van-swipe-cell v-for="budget in incomeBudgets" :key="budget.id">
|
||||||
@@ -115,9 +115,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<van-empty v-else description="暂无收入预算" />
|
<van-empty v-else description="暂无收入预算" />
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 底部安全距离 -->
|
||||||
|
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
||||||
|
</div>
|
||||||
</van-tab>
|
</van-tab>
|
||||||
|
|
||||||
<van-tab title="存款" :name="BudgetCategory.Savings">
|
<van-tab title="存款" :name="BudgetCategory.Savings">
|
||||||
|
<div class="scroll-content">
|
||||||
<div class="budget-list">
|
<div class="budget-list">
|
||||||
<template v-if="savingsBudgets?.length > 0">
|
<template v-if="savingsBudgets?.length > 0">
|
||||||
<van-swipe-cell v-for="budget in savingsBudgets" :key="budget.id">
|
<van-swipe-cell v-for="budget in savingsBudgets" :key="budget.id">
|
||||||
@@ -162,11 +166,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<van-empty v-else description="暂无存款计划" />
|
<van-empty v-else description="暂无存款计划" />
|
||||||
</div>
|
</div>
|
||||||
</van-tab>
|
|
||||||
</van-tabs>
|
|
||||||
<!-- 底部安全距离 -->
|
<!-- 底部安全距离 -->
|
||||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</van-tab>
|
||||||
|
</van-tabs>
|
||||||
|
|
||||||
<!-- 添加/编辑预算弹窗 -->
|
<!-- 添加/编辑预算弹窗 -->
|
||||||
<BudgetEditPopup
|
<BudgetEditPopup
|
||||||
@@ -178,7 +182,6 @@
|
|||||||
@success="fetchBudgetList"
|
@success="fetchBudgetList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -226,7 +229,6 @@ const overallStats = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
week: getStatsForType(BudgetPeriodType.Week),
|
|
||||||
month: getStatsForType(BudgetPeriodType.Month),
|
month: getStatsForType(BudgetPeriodType.Month),
|
||||||
year: getStatsForType(BudgetPeriodType.Year)
|
year: getStatsForType(BudgetPeriodType.Year)
|
||||||
}
|
}
|
||||||
@@ -274,10 +276,8 @@ const formatMoney = (val) => {
|
|||||||
|
|
||||||
const getPeriodLabel = (type) => {
|
const getPeriodLabel = (type) => {
|
||||||
const map = {
|
const map = {
|
||||||
[BudgetPeriodType.Week]: '本周',
|
|
||||||
[BudgetPeriodType.Month]: '本月',
|
[BudgetPeriodType.Month]: '本月',
|
||||||
[BudgetPeriodType.Year]: '本年',
|
[BudgetPeriodType.Year]: '本年'
|
||||||
[BudgetPeriodType.Longterm]: '长期'
|
|
||||||
}
|
}
|
||||||
return map[type] || '周期'
|
return map[type] || '周期'
|
||||||
}
|
}
|
||||||
@@ -298,18 +298,11 @@ const getIncomeProgressColor = (budget) => {
|
|||||||
const refDateMap = {}
|
const refDateMap = {}
|
||||||
|
|
||||||
const handleSwitchPeriod = async (budget, direction) => {
|
const handleSwitchPeriod = async (budget, direction) => {
|
||||||
if (budget.type === BudgetPeriodType.Longterm) {
|
|
||||||
showToast('长期预算不支持切换周期')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取或初始化该预算的参考日期
|
// 获取或初始化该预算的参考日期
|
||||||
let currentRefDate = refDateMap[budget.id] || new Date()
|
let currentRefDate = refDateMap[budget.id] || new Date()
|
||||||
const date = new Date(currentRefDate)
|
const date = new Date(currentRefDate)
|
||||||
|
|
||||||
if (budget.type === BudgetPeriodType.Week) {
|
if (budget.type === BudgetPeriodType.Month) {
|
||||||
date.setDate(date.getDate() + direction * 7)
|
|
||||||
} else if (budget.type === BudgetPeriodType.Month) {
|
|
||||||
date.setMonth(date.getMonth() + direction)
|
date.setMonth(date.getMonth() + direction)
|
||||||
} else if (budget.type === BudgetPeriodType.Year) {
|
} else if (budget.type === BudgetPeriodType.Year) {
|
||||||
date.setFullYear(date.getFullYear() + direction)
|
date.setFullYear(date.getFullYear() + direction)
|
||||||
@@ -362,6 +355,27 @@ const handleToggleStop = async (budget) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.budget-tabs {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-tabs__content) {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.van-tab__panel) {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.budget-list {
|
.budget-list {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
|||||||
@@ -192,36 +192,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<!-- 分类按钮网格 -->
|
<!-- 分类选择组件 -->
|
||||||
<div class="classify-buttons">
|
<ClassifySelector
|
||||||
<van-button
|
v-model="form.classify"
|
||||||
type="success"
|
:type="form.type"
|
||||||
size="small"
|
/>
|
||||||
class="classify-btn"
|
|
||||||
@click="addClassifyDialogRef.open()"
|
|
||||||
>
|
|
||||||
+ 新增
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-for="item in classifyColumns"
|
|
||||||
:key="item.id"
|
|
||||||
:type="form.classify === item.text ? 'primary' : 'default'"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="selectClassify(item.text)"
|
|
||||||
>
|
|
||||||
{{ item.text }}
|
|
||||||
</van-button>
|
|
||||||
<van-button
|
|
||||||
v-if="form.classify"
|
|
||||||
type="warning"
|
|
||||||
size="small"
|
|
||||||
class="classify-btn"
|
|
||||||
@click="clearClassify"
|
|
||||||
>
|
|
||||||
清空
|
|
||||||
</van-button>
|
|
||||||
</div>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</van-form>
|
</van-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -257,12 +232,6 @@
|
|||||||
@cancel="showMonthDaysPicker = false"
|
@cancel="showMonthDaysPicker = false"
|
||||||
/>
|
/>
|
||||||
</van-popup>
|
</van-popup>
|
||||||
|
|
||||||
<!-- 新增分类对话框 -->
|
|
||||||
<AddClassifyDialog
|
|
||||||
ref="addClassifyDialogRef"
|
|
||||||
@confirm="handleAddClassify"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -275,13 +244,11 @@ import {
|
|||||||
deletePeriodic as deletePeriodicApi,
|
deletePeriodic as deletePeriodicApi,
|
||||||
togglePeriodicEnabled
|
togglePeriodicEnabled
|
||||||
} from '@/api/transactionPeriodic'
|
} from '@/api/transactionPeriodic'
|
||||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
|
||||||
import PopupContainer from '@/components/PopupContainer.vue'
|
import PopupContainer from '@/components/PopupContainer.vue'
|
||||||
import AddClassifyDialog from '@/components/AddClassifyDialog.vue'
|
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const navTitle = ref('周期账单')
|
const navTitle = ref('周期账单')
|
||||||
const addClassifyDialogRef = ref()
|
|
||||||
|
|
||||||
const periodicList = ref([])
|
const periodicList = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -299,9 +266,6 @@ const showPeriodicTypePicker = ref(false)
|
|||||||
const showWeekdaysPicker = ref(false)
|
const showWeekdaysPicker = ref(false)
|
||||||
const showMonthDaysPicker = ref(false)
|
const showMonthDaysPicker = ref(false)
|
||||||
|
|
||||||
// 分类列表
|
|
||||||
const classifyColumns = ref([])
|
|
||||||
|
|
||||||
// 周期类型
|
// 周期类型
|
||||||
const periodicTypeColumns = [
|
const periodicTypeColumns = [
|
||||||
{ text: '每天', value: 0 },
|
{ text: '每天', value: 0 },
|
||||||
@@ -459,24 +423,6 @@ const openAddDialog = () => {
|
|||||||
isEdit.value = false
|
isEdit.value = false
|
||||||
resetForm()
|
resetForm()
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
// 加载分类列表
|
|
||||||
loadClassifyList(form.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载分类列表
|
|
||||||
const loadClassifyList = async (type = null) => {
|
|
||||||
try {
|
|
||||||
const response = await getCategoryList(type)
|
|
||||||
if (response.success) {
|
|
||||||
classifyColumns.value = (response.data || []).map(item => ({
|
|
||||||
text: item.name,
|
|
||||||
value: item.name,
|
|
||||||
id: item.id
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载分类列表出错:', error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑
|
// 编辑
|
||||||
@@ -490,9 +436,6 @@ const editPeriodic = (item) => {
|
|||||||
form.periodicType = item.periodicType
|
form.periodicType = item.periodicType
|
||||||
form.periodicTypeText = periodicTypeColumns.find(t => t.value === item.periodicType)?.text || ''
|
form.periodicTypeText = periodicTypeColumns.find(t => t.value === item.periodicType)?.text || ''
|
||||||
|
|
||||||
// 加载对应类型的分类列表
|
|
||||||
loadClassifyList(item.type)
|
|
||||||
|
|
||||||
// 解析周期配置
|
// 解析周期配置
|
||||||
if (item.periodicConfig) {
|
if (item.periodicConfig) {
|
||||||
switch (item.periodicType) {
|
switch (item.periodicType) {
|
||||||
@@ -607,40 +550,6 @@ const onMonthDaysConfirm = ({ selectedValues, selectedOptions }) => {
|
|||||||
showMonthDaysPicker.value = false
|
showMonthDaysPicker.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择分类
|
|
||||||
const selectClassify = (classify) => {
|
|
||||||
form.classify = classify
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空分类
|
|
||||||
const clearClassify = () => {
|
|
||||||
form.classify = ''
|
|
||||||
showToast('已清空分类')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增分类
|
|
||||||
const handleAddClassify = async (categoryName) => {
|
|
||||||
try {
|
|
||||||
// 调用API创建分类
|
|
||||||
const response = await createCategory({
|
|
||||||
name: categoryName,
|
|
||||||
type: form.type
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
showToast('分类创建成功')
|
|
||||||
// 重新加载分类列表
|
|
||||||
await loadClassifyList(form.type)
|
|
||||||
form.classify = categoryName
|
|
||||||
} else {
|
|
||||||
showToast(response.message || '创建分类失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建分类出错:', error)
|
|
||||||
showToast('创建分类失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -679,17 +588,4 @@ const handleAddClassify = async (categoryName) => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.classify-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classify-btn {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
min-width: 70px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ public class BudgetController(
|
|||||||
factor = b.Type switch
|
factor = b.Type switch
|
||||||
{
|
{
|
||||||
BudgetPeriodType.Month => 12,
|
BudgetPeriodType.Month => 12,
|
||||||
BudgetPeriodType.Week => 52,
|
|
||||||
BudgetPeriodType.Year => 1,
|
BudgetPeriodType.Year => 1,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
@@ -191,7 +190,6 @@ public class BudgetController(
|
|||||||
factor = b.Type switch
|
factor = b.Type switch
|
||||||
{
|
{
|
||||||
BudgetPeriodType.Month => 1,
|
BudgetPeriodType.Month => 1,
|
||||||
BudgetPeriodType.Week => 52m / 12m,
|
|
||||||
BudgetPeriodType.Year => 1m / 12m,
|
BudgetPeriodType.Year => 1m / 12m,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ public class BudgetDto
|
|||||||
StartDate = entity.StartDate.ToString("yyyy-MM-dd"),
|
StartDate = entity.StartDate.ToString("yyyy-MM-dd"),
|
||||||
Period = entity.Type switch
|
Period = entity.Type switch
|
||||||
{
|
{
|
||||||
BudgetPeriodType.Longterm => "长期",
|
|
||||||
BudgetPeriodType.Year => $"{start:yy}年",
|
BudgetPeriodType.Year => $"{start:yy}年",
|
||||||
BudgetPeriodType.Month => $"{start:yy}年第{start.Month}月",
|
BudgetPeriodType.Month => $"{start:yy}年第{start.Month}月",
|
||||||
BudgetPeriodType.Week => $"{start:yy}年第{System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(start, System.Globalization.CalendarWeekRule.FirstDay, DayOfWeek.Monday)}周",
|
|
||||||
_ => $"{start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}"
|
_ => $"{start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}"
|
||||||
},
|
},
|
||||||
PeriodStart = start,
|
PeriodStart = start,
|
||||||
|
|||||||
Reference in New Issue
Block a user