封装调整
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
|
||||
{
|
||||
/// <summary>
|
||||
/// 周
|
||||
/// </summary>
|
||||
Week,
|
||||
/// <summary>
|
||||
/// 月
|
||||
/// </summary>
|
||||
Month,
|
||||
Month = 1,
|
||||
/// <summary>
|
||||
/// 年
|
||||
/// </summary>
|
||||
Year,
|
||||
/// <summary>
|
||||
/// 长期
|
||||
/// </summary>
|
||||
Longterm
|
||||
Year = 2
|
||||
}
|
||||
|
||||
public enum BudgetCategory
|
||||
|
||||
@@ -61,19 +61,10 @@ public class BudgetService(
|
||||
|
||||
public static (DateTime start, DateTime end) GetPeriodRange(DateTime startDate, BudgetPeriodType type, DateTime referenceDate)
|
||||
{
|
||||
if (type == BudgetPeriodType.Longterm) return (startDate, DateTime.MaxValue);
|
||||
|
||||
DateTime start;
|
||||
DateTime end;
|
||||
|
||||
if (type == BudgetPeriodType.Week)
|
||||
{
|
||||
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)
|
||||
if (type == BudgetPeriodType.Month)
|
||||
{
|
||||
start = new DateTime(referenceDate.Year, referenceDate.Month, 1);
|
||||
end = start.AddMonths(1).AddDays(-1).AddHours(23).AddMinutes(59).AddSeconds(59);
|
||||
|
||||
@@ -52,27 +52,11 @@
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<!-- 分类按钮网格 -->
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
type="success"
|
||||
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>
|
||||
<!-- 分类选择组件 -->
|
||||
<ClassifySelector
|
||||
v-model="categoryName"
|
||||
:type="form.type"
|
||||
/>
|
||||
</van-cell-group>
|
||||
|
||||
<div class="actions">
|
||||
@@ -83,12 +67,6 @@
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
<!-- 新增分类对话框 -->
|
||||
<AddClassifyDialog
|
||||
ref="addClassifyDialogRef"
|
||||
@confirm="handleAddClassify"
|
||||
/>
|
||||
|
||||
<!-- 日期选择弹窗 -->
|
||||
<van-popup v-model:show="showDatePicker" position="bottom" round teleport="body">
|
||||
<van-date-picker
|
||||
@@ -115,8 +93,7 @@
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import dayjs from 'dayjs'
|
||||
import AddClassifyDialog from '@/components/AddClassifyDialog.vue'
|
||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
||||
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||
|
||||
const props = defineProps({
|
||||
initialData: {
|
||||
@@ -135,13 +112,10 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['submit'])
|
||||
|
||||
const addClassifyDialogRef = ref()
|
||||
|
||||
// 表单数据
|
||||
const form = ref({
|
||||
type: 0, // 0: 支出, 1: 收入, 2: 不计
|
||||
amount: '',
|
||||
categoryId: null,
|
||||
date: dayjs().format('YYYY-MM-DD'),
|
||||
time: dayjs().format('HH:mm'),
|
||||
note: ''
|
||||
@@ -153,10 +127,7 @@ const categoryName = ref('')
|
||||
const showDatePicker = ref(false)
|
||||
const showTimePicker = ref(false)
|
||||
|
||||
// 选择器数据
|
||||
const categoryList = ref([])
|
||||
|
||||
// 日期时间临时变量 (Vant DatePicker 需要数组或特定格式)
|
||||
// 日期时间临时变量 (Vant DatePicker 需要数组 or 特定格式)
|
||||
const currentDate = ref(dayjs().format('YYYY-MM-DD').split('-'))
|
||||
const currentTime = ref(dayjs().format('HH:mm').split(':'))
|
||||
|
||||
@@ -177,29 +148,11 @@ const initForm = async () => {
|
||||
if (reason !== undefined) form.value.note = reason
|
||||
if (type !== undefined) form.value.type = type
|
||||
|
||||
// 加载分类列表
|
||||
await loadClassifyList(form.value.type)
|
||||
|
||||
// 如果有传入分类名称,尝试匹配
|
||||
// 如果有传入分类名称,尝试设置
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await loadClassifyList(form.value.type)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -213,50 +166,6 @@ watch(() => props.initialData, () => {
|
||||
|
||||
const handleTypeChange = (newType) => {
|
||||
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 }) => {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<span class="percent" :class="percentClass">{{ percentage }}%</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div v-if="budget.type !== BudgetPeriodType.Longterm" class="progress-section time-progress">
|
||||
<div class="progress-section time-progress">
|
||||
<span class="period-type">时间进度</span>
|
||||
<van-progress
|
||||
:percentage="timePercentage"
|
||||
@@ -126,7 +126,7 @@ const percentage = 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 end = new Date(props.budget.periodEnd).getTime()
|
||||
const now = new Date().getTime()
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<van-field name="type" label="统计周期">
|
||||
<template #input>
|
||||
<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.Year">年</van-radio>
|
||||
</van-radio-group>
|
||||
@@ -45,29 +44,13 @@
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
v-if="filteredCategories.length > 0"
|
||||
:type="isAllSelected ? 'primary' : 'default'"
|
||||
size="small"
|
||||
class="classify-btn all-btn"
|
||||
@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>
|
||||
|
||||
<ClassifySelector
|
||||
v-model="form.selectedCategories"
|
||||
:type="budgetType"
|
||||
multiple
|
||||
:show-add="false"
|
||||
:show-clear="false"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
</div>
|
||||
@@ -78,19 +61,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import { getCategoryList } from '@/api/transactionCategory'
|
||||
import { createBudget, updateBudget } from '@/api/budget'
|
||||
import { BudgetPeriodType, BudgetCategory } from '@/constants/enums'
|
||||
import PopupContainer from '@/components/PopupContainer.vue'
|
||||
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const visible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
|
||||
const categories = ref([])
|
||||
const form = reactive({
|
||||
id: undefined,
|
||||
name: '',
|
||||
@@ -137,44 +119,10 @@ defineExpose({
|
||||
open
|
||||
})
|
||||
|
||||
const filteredCategories = computed(() => {
|
||||
const targetType = form.category === BudgetCategory.Expense ? 0 : (form.category === BudgetCategory.Income ? 1 : 2)
|
||||
return categories.value.filter(c => c.type === targetType)
|
||||
const budgetType = computed(() => {
|
||||
return form.category === BudgetCategory.Expense ? 0 : (form.category === BudgetCategory.Income ? 1 : 2)
|
||||
})
|
||||
|
||||
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 () => {
|
||||
try {
|
||||
const data = {
|
||||
@@ -207,10 +155,6 @@ const getCategoryName = (category) => {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchCategories()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -235,29 +179,9 @@ onMounted(() => {
|
||||
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 {
|
||||
font-size: 13px;
|
||||
color: #969799;
|
||||
padding: 8px 0;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -30,7 +30,6 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const periodConfigs = {
|
||||
week: { label: '本周', showDivider: true },
|
||||
month: { label: '本月', showDivider: true },
|
||||
year: { label: '年度', showDivider: false }
|
||||
}
|
||||
|
||||
@@ -11,29 +11,13 @@
|
||||
|
||||
<div class="category-section">
|
||||
<div class="section-title">可多选分类</div>
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
v-if="incomeCategories.length > 0"
|
||||
:type="isAllSelected ? 'primary' : 'default'"
|
||||
size="small"
|
||||
class="classify-btn all-btn"
|
||||
@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>
|
||||
<ClassifySelector
|
||||
v-model="selectedCategories"
|
||||
:type="2"
|
||||
multiple
|
||||
:show-add="false"
|
||||
:show-clear="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,21 +28,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { showToast, showLoadingToast, closeToast } from 'vant'
|
||||
import { getCategoryList } from '@/api/transactionCategory'
|
||||
import { getConfig, setConfig } from '@/api/config'
|
||||
import PopupContainer from '@/components/PopupContainer.vue'
|
||||
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const visible = ref(false)
|
||||
const categories = ref([])
|
||||
const selectedCategories = ref([])
|
||||
|
||||
const open = async () => {
|
||||
visible.value = true
|
||||
await fetchCategories()
|
||||
await fetchConfig()
|
||||
}
|
||||
|
||||
@@ -66,43 +48,6 @@ defineExpose({
|
||||
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 () => {
|
||||
try {
|
||||
const res = await getConfig('SavingsCategories')
|
||||
@@ -156,22 +101,6 @@ const onSubmit = async () => {
|
||||
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 {
|
||||
text-align: center;
|
||||
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>
|
||||
</van-field>
|
||||
|
||||
<!-- 分类按钮网格 -->
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
type="success"
|
||||
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>
|
||||
<!-- 分类选择组件 -->
|
||||
<ClassifySelector
|
||||
v-model="batchForm.classify"
|
||||
:type="batchForm.type"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
<template #footer>
|
||||
@@ -172,12 +147,6 @@
|
||||
</van-button>
|
||||
</template>
|
||||
</PopupContainer>
|
||||
|
||||
<!-- 新增分类对话框 -->
|
||||
<AddClassifyDialog
|
||||
ref="addClassifyDialogRef"
|
||||
@confirm="handleAddClassify"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -191,8 +160,7 @@ import {
|
||||
showConfirmDialog
|
||||
} from 'vant'
|
||||
import { getReasonGroups, batchUpdateByReason, getTransactionList } from '@/api/transactionRecord'
|
||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
||||
import AddClassifyDialog from './AddClassifyDialog.vue'
|
||||
import ClassifySelector from './ClassifySelector.vue'
|
||||
import TransactionList from './TransactionList.vue'
|
||||
import TransactionDetail from './TransactionDetail.vue'
|
||||
import PopupContainer from './PopupContainer.vue'
|
||||
@@ -219,8 +187,6 @@ const selectedReasons = ref(new Set())
|
||||
const pageIndex = ref(1)
|
||||
const finished = ref(false)
|
||||
const total = ref(0)
|
||||
const categories = ref([])
|
||||
|
||||
// 弹窗状态
|
||||
const showTransactionList = ref(false)
|
||||
const showTransactionDetail = ref(false)
|
||||
@@ -239,27 +205,15 @@ const transactionPageSize = ref(20)
|
||||
const showBatchDialog = ref(false)
|
||||
const batchFormRef = ref(null)
|
||||
const batchGroup = ref(null)
|
||||
const addClassifyDialogRef = ref()
|
||||
const batchForm = ref({
|
||||
type: null,
|
||||
typeName: '',
|
||||
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) => {
|
||||
batchForm.value.classify = ''
|
||||
if (newVal !== null) {
|
||||
loadCategories(newVal)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取类型名称
|
||||
@@ -361,62 +315,9 @@ const handleBatchClassify = (group) => {
|
||||
typeName: getTypeName(group.sampleType),
|
||||
classify: group.sampleClassify || ''
|
||||
}
|
||||
// 加载对应类型的分类列表
|
||||
loadCategories(group.sampleType)
|
||||
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 () => {
|
||||
try {
|
||||
@@ -744,20 +645,5 @@ defineExpose({
|
||||
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>
|
||||
|
||||
@@ -61,36 +61,12 @@
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<!-- 分类按钮网格 -->
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
type="success"
|
||||
size="small"
|
||||
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>
|
||||
<!-- 分类选择组件 -->
|
||||
<ClassifySelector
|
||||
v-model="editForm.classify"
|
||||
:type="editForm.type"
|
||||
@change="handleClassifyChange"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
|
||||
@@ -107,12 +83,6 @@
|
||||
</template>
|
||||
</PopupContainer>
|
||||
|
||||
<!-- 新增分类对话框 -->
|
||||
<AddClassifyDialog
|
||||
ref="addClassifyDialogRef"
|
||||
@confirm="handleAddClassify"
|
||||
/>
|
||||
|
||||
<!-- 抵账候选列表弹窗 -->
|
||||
<PopupContainer
|
||||
v-model="showOffsetPopup"
|
||||
@@ -138,9 +108,8 @@
|
||||
import { ref, reactive, watch, defineProps, defineEmits } from 'vue'
|
||||
import { showToast, showConfirmDialog } from 'vant'
|
||||
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 { getCategoryList, createCategory } from '@/api/transactionCategory'
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
@@ -157,10 +126,6 @@ const emit = defineEmits(['update:show', 'save'])
|
||||
|
||||
const visible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const addClassifyDialogRef = ref()
|
||||
|
||||
// 分类相关
|
||||
const classifyColumns = ref([])
|
||||
|
||||
// 编辑表单
|
||||
const editForm = reactive({
|
||||
@@ -186,9 +151,6 @@ watch(() => props.transaction, (newVal) => {
|
||||
editForm.balance = String(newVal.balance)
|
||||
editForm.type = newVal.type
|
||||
editForm.classify = newVal.classify || ''
|
||||
|
||||
// 根据交易类型加载分类
|
||||
loadClassifyList(newVal.type)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -200,26 +162,8 @@ watch(visible, (newVal) => {
|
||||
watch(() => editForm.type, (newVal) => {
|
||||
// 清空已选的分类
|
||||
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 () => {
|
||||
try {
|
||||
@@ -239,8 +183,6 @@ const onSubmit = async () => {
|
||||
showToast('保存成功')
|
||||
visible.value = false
|
||||
emit('save', data)
|
||||
// 重新加载分类列表
|
||||
await loadClassifyList(editForm.type)
|
||||
} else {
|
||||
showToast(response.message || '保存失败')
|
||||
}
|
||||
@@ -252,47 +194,15 @@ const onSubmit = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 选择分类
|
||||
const selectClassify = (classify) => {
|
||||
editForm.classify = classify
|
||||
|
||||
// 分类选择变化
|
||||
const handleClassifyChange = () => {
|
||||
if (editForm.id > 0 && editForm.type >= 0) {
|
||||
// 直接保存
|
||||
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) => {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
@@ -352,16 +262,4 @@ const handleCandidateSelect = (candidate) => {
|
||||
</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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
* 预算周期类型
|
||||
*/
|
||||
export const BudgetPeriodType = {
|
||||
Week: 0,
|
||||
Month: 1,
|
||||
Year: 2,
|
||||
Longterm: 3
|
||||
Year: 2
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
</template>
|
||||
</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
|
||||
v-if="activeTab !== BudgetCategory.Savings"
|
||||
:stats="overallStats"
|
||||
:title="activeTabTitle"
|
||||
: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">
|
||||
<template v-if="expenseBudgets?.length > 0">
|
||||
<van-swipe-cell v-for="budget in expenseBudgets" :key="budget.id">
|
||||
@@ -66,14 +67,13 @@
|
||||
</template>
|
||||
<van-empty v-else description="暂无支出预算" />
|
||||
</div>
|
||||
<!-- 底部安全距离 -->
|
||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab title="收入" :name="BudgetCategory.Income">
|
||||
<BudgetSummary
|
||||
:stats="overallStats"
|
||||
:title="activeTabTitle"
|
||||
:get-value-class="getValueClass"
|
||||
/>
|
||||
<div class="scroll-content">
|
||||
<div class="budget-list">
|
||||
<template v-if="incomeBudgets?.length > 0">
|
||||
<van-swipe-cell v-for="budget in incomeBudgets" :key="budget.id">
|
||||
@@ -115,9 +115,13 @@
|
||||
</template>
|
||||
<van-empty v-else description="暂无收入预算" />
|
||||
</div>
|
||||
<!-- 底部安全距离 -->
|
||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
||||
</div>
|
||||
</van-tab>
|
||||
|
||||
<van-tab title="存款" :name="BudgetCategory.Savings">
|
||||
<div class="scroll-content">
|
||||
<div class="budget-list">
|
||||
<template v-if="savingsBudgets?.length > 0">
|
||||
<van-swipe-cell v-for="budget in savingsBudgets" :key="budget.id">
|
||||
@@ -162,11 +166,11 @@
|
||||
</template>
|
||||
<van-empty v-else description="暂无存款计划" />
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
<!-- 底部安全距离 -->
|
||||
<div style="height: calc(50px + env(safe-area-inset-bottom, 0px))"></div>
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
|
||||
<!-- 添加/编辑预算弹窗 -->
|
||||
<BudgetEditPopup
|
||||
@@ -178,7 +182,6 @@
|
||||
@success="fetchBudgetList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -226,7 +229,6 @@ const overallStats = computed(() => {
|
||||
}
|
||||
|
||||
return {
|
||||
week: getStatsForType(BudgetPeriodType.Week),
|
||||
month: getStatsForType(BudgetPeriodType.Month),
|
||||
year: getStatsForType(BudgetPeriodType.Year)
|
||||
}
|
||||
@@ -274,10 +276,8 @@ const formatMoney = (val) => {
|
||||
|
||||
const getPeriodLabel = (type) => {
|
||||
const map = {
|
||||
[BudgetPeriodType.Week]: '本周',
|
||||
[BudgetPeriodType.Month]: '本月',
|
||||
[BudgetPeriodType.Year]: '本年',
|
||||
[BudgetPeriodType.Longterm]: '长期'
|
||||
[BudgetPeriodType.Year]: '本年'
|
||||
}
|
||||
return map[type] || '周期'
|
||||
}
|
||||
@@ -298,18 +298,11 @@ const getIncomeProgressColor = (budget) => {
|
||||
const refDateMap = {}
|
||||
|
||||
const handleSwitchPeriod = async (budget, direction) => {
|
||||
if (budget.type === BudgetPeriodType.Longterm) {
|
||||
showToast('长期预算不支持切换周期')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取或初始化该预算的参考日期
|
||||
let currentRefDate = refDateMap[budget.id] || new Date()
|
||||
const date = new Date(currentRefDate)
|
||||
|
||||
if (budget.type === BudgetPeriodType.Week) {
|
||||
date.setDate(date.getDate() + direction * 7)
|
||||
} else if (budget.type === BudgetPeriodType.Month) {
|
||||
if (budget.type === BudgetPeriodType.Month) {
|
||||
date.setMonth(date.getMonth() + direction)
|
||||
} else if (budget.type === BudgetPeriodType.Year) {
|
||||
date.setFullYear(date.getFullYear() + direction)
|
||||
@@ -362,6 +355,27 @@ const handleToggleStop = async (budget) => {
|
||||
</script>
|
||||
|
||||
<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 {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 20px;
|
||||
|
||||
@@ -192,36 +192,11 @@
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<!-- 分类按钮网格 -->
|
||||
<div class="classify-buttons">
|
||||
<van-button
|
||||
type="success"
|
||||
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>
|
||||
<!-- 分类选择组件 -->
|
||||
<ClassifySelector
|
||||
v-model="form.classify"
|
||||
:type="form.type"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
<template #footer>
|
||||
@@ -257,12 +232,6 @@
|
||||
@cancel="showMonthDaysPicker = false"
|
||||
/>
|
||||
</van-popup>
|
||||
|
||||
<!-- 新增分类对话框 -->
|
||||
<AddClassifyDialog
|
||||
ref="addClassifyDialogRef"
|
||||
@confirm="handleAddClassify"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -275,13 +244,11 @@ import {
|
||||
deletePeriodic as deletePeriodicApi,
|
||||
togglePeriodicEnabled
|
||||
} from '@/api/transactionPeriodic'
|
||||
import { getCategoryList, createCategory } from '@/api/transactionCategory'
|
||||
import PopupContainer from '@/components/PopupContainer.vue'
|
||||
import AddClassifyDialog from '@/components/AddClassifyDialog.vue'
|
||||
import ClassifySelector from '@/components/ClassifySelector.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const navTitle = ref('周期账单')
|
||||
const addClassifyDialogRef = ref()
|
||||
|
||||
const periodicList = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -299,9 +266,6 @@ const showPeriodicTypePicker = ref(false)
|
||||
const showWeekdaysPicker = ref(false)
|
||||
const showMonthDaysPicker = ref(false)
|
||||
|
||||
// 分类列表
|
||||
const classifyColumns = ref([])
|
||||
|
||||
// 周期类型
|
||||
const periodicTypeColumns = [
|
||||
{ text: '每天', value: 0 },
|
||||
@@ -459,24 +423,6 @@ const openAddDialog = () => {
|
||||
isEdit.value = false
|
||||
resetForm()
|
||||
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.periodicTypeText = periodicTypeColumns.find(t => t.value === item.periodicType)?.text || ''
|
||||
|
||||
// 加载对应类型的分类列表
|
||||
loadClassifyList(item.type)
|
||||
|
||||
// 解析周期配置
|
||||
if (item.periodicConfig) {
|
||||
switch (item.periodicType) {
|
||||
@@ -607,40 +550,6 @@ const onMonthDaysConfirm = ({ selectedValues, selectedOptions }) => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
@@ -679,17 +588,4 @@ const handleAddClassify = async (categoryName) => {
|
||||
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>
|
||||
|
||||
@@ -181,7 +181,6 @@ public class BudgetController(
|
||||
factor = b.Type switch
|
||||
{
|
||||
BudgetPeriodType.Month => 12,
|
||||
BudgetPeriodType.Week => 52,
|
||||
BudgetPeriodType.Year => 1,
|
||||
_ => 0
|
||||
};
|
||||
@@ -191,7 +190,6 @@ public class BudgetController(
|
||||
factor = b.Type switch
|
||||
{
|
||||
BudgetPeriodType.Month => 1,
|
||||
BudgetPeriodType.Week => 52m / 12m,
|
||||
BudgetPeriodType.Year => 1m / 12m,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
@@ -35,10 +35,8 @@ public class BudgetDto
|
||||
StartDate = entity.StartDate.ToString("yyyy-MM-dd"),
|
||||
Period = entity.Type switch
|
||||
{
|
||||
BudgetPeriodType.Longterm => "长期",
|
||||
BudgetPeriodType.Year => $"{start:yy}年",
|
||||
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}"
|
||||
},
|
||||
PeriodStart = start,
|
||||
|
||||
Reference in New Issue
Block a user