feat: 初始化收支管理系统
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<script setup lang="ts">
|
||||
// 分类管理组件:展示收入/支出分类列表,支持新增、编辑、删除分类
|
||||
import { ref, onMounted, computed, h } from 'vue'
|
||||
import { useMessage, useDialog, NButton, NSpace, NDataTable, NModal, NForm, NFormItem, NInput, NInputNumber, NSelect } from 'naive-ui'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { getCategoryList, createCategory, updateCategory, deleteCategory } from '../api/category'
|
||||
|
||||
const message = useMessage() // 消息提示
|
||||
const dialog = useDialog() // 对话框
|
||||
|
||||
/** 分类数据结构 */
|
||||
interface CategoryItem {
|
||||
id: number
|
||||
name: string
|
||||
type: string // INCOME 或 EXPENSE
|
||||
icon: string
|
||||
sortOrder: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// 列表和弹窗状态
|
||||
const categories = ref<CategoryItem[]>([]) // 全部分类列表
|
||||
const loading = ref(false) // 列表加载状态
|
||||
const showModal = ref(false) // 新增/编辑弹窗显示状态
|
||||
const editingId = ref<number | null>(null) // 当前编辑的分类 ID(null 表示新增)
|
||||
const form = ref({ name: '', type: 'EXPENSE', icon: '', sortOrder: 0 }) // 表单数据
|
||||
|
||||
// 类型选项
|
||||
const typeOptions = [
|
||||
{ label: '支出', value: 'EXPENSE' },
|
||||
{ label: '收入', value: 'INCOME' },
|
||||
]
|
||||
|
||||
// 按类型过滤分类列表
|
||||
const incomeList = computed(() => categories.value.filter(c => c.type === 'INCOME')) // 收入分类
|
||||
const expenseList = computed(() => categories.value.filter(c => c.type === 'EXPENSE')) // 支出分类
|
||||
|
||||
/** 创建表格列配置 */
|
||||
function createColumns(): DataTableColumns<CategoryItem> {
|
||||
return [
|
||||
{ title: '名称', key: 'name' },
|
||||
{ title: '图标', key: 'icon' },
|
||||
{ title: '排序', key: 'sortOrder', width: 80 },
|
||||
{
|
||||
title: '操作', key: 'actions', width: 180,
|
||||
render(row) {
|
||||
// 编辑和删除按钮
|
||||
return [
|
||||
h(NSpace, null, {
|
||||
default: () => [
|
||||
h(NButton, { size: 'small', onClick: () => handleEdit(row) }, { default: () => '编辑' }),
|
||||
h(NButton, { size: 'small', type: 'error', onClick: () => handleDelete(row.id) }, { default: () => '删除' }),
|
||||
],
|
||||
}),
|
||||
]
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const columns = createColumns() // 表格列配置
|
||||
|
||||
/** 加载分类列表数据 */
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await getCategoryList()
|
||||
categories.value = res.data
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开新增分类弹窗,重置表单 */
|
||||
function handleAdd() {
|
||||
editingId.value = null
|
||||
form.value = { name: '', type: 'EXPENSE', icon: '', sortOrder: 0 }
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
/** 打开编辑分类弹窗,填充表单数据 */
|
||||
function handleEdit(row: CategoryItem) {
|
||||
editingId.value = row.id
|
||||
form.value = { name: row.name, type: row.type, icon: row.icon || '', sortOrder: row.sortOrder }
|
||||
showModal.value = true
|
||||
}
|
||||
|
||||
/** 提交表单:校验 -> 新增或更新 -> 刷新列表 */
|
||||
async function handleSubmit() {
|
||||
if (!form.value.name) {
|
||||
message.warning('请输入分类名称')
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (editingId.value) {
|
||||
// 更新已有分类
|
||||
await updateCategory(editingId.value, form.value)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
// 创建新分类
|
||||
await createCategory(form.value)
|
||||
message.success('创建成功')
|
||||
}
|
||||
showModal.value = false
|
||||
await fetchData() // 刷新列表
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除分类:弹出确认对话框,确认后执行删除并刷新列表 */
|
||||
function handleDelete(id: number) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个分类吗?',
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await deleteCategory(id)
|
||||
message.success('删除成功')
|
||||
await fetchData()
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时加载分类数据
|
||||
onMounted(fetchData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 顶部:标题和新增按钮 -->
|
||||
<NSpace justify="space-between" style="margin-bottom: 16px;">
|
||||
<h2 style="margin: 0;">分类管理</h2>
|
||||
<NButton type="primary" @click="handleAdd">新增分类</NButton>
|
||||
</NSpace>
|
||||
|
||||
<!-- 支出分类列表 -->
|
||||
<h3>支出分类</h3>
|
||||
<NDataTable :columns="columns" :data="expenseList" :loading="loading" :bordered="false" />
|
||||
|
||||
<!-- 收入分类列表 -->
|
||||
<h3 style="margin-top: 24px;">收入分类</h3>
|
||||
<NDataTable :columns="columns" :data="incomeList" :loading="loading" :bordered="false" />
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<NModal v-model:show="showModal" preset="card" :title="editingId ? '编辑分类' : '新增分类'" style="width: 450px;">
|
||||
<NForm>
|
||||
<NFormItem label="分类名称">
|
||||
<NInput v-model:value="form.name" placeholder="请输入分类名称" />
|
||||
</NFormItem>
|
||||
<NFormItem label="类型">
|
||||
<NSelect v-model:value="form.type" :options="typeOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="图标">
|
||||
<NInput v-model:value="form.icon" placeholder="可选,如 emoji" />
|
||||
</NFormItem>
|
||||
<NFormItem label="排序">
|
||||
<NInputNumber v-model:value="form.sortOrder" :min="0" />
|
||||
</NFormItem>
|
||||
<NButton type="primary" block @click="handleSubmit">确定</NButton>
|
||||
</NForm>
|
||||
</NModal>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user