122 lines
5.8 KiB
Vue
122 lines
5.8 KiB
Vue
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { api } from '../api'
|
||
import { message } from 'ant-design-vue'
|
||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||
|
||
interface Cat { id: number; name: string; iconKey: string; type: string; sortOrder: number; isDeleted: boolean }
|
||
|
||
const list = ref<Cat[]>([])
|
||
const loading = ref(true)
|
||
const modalVisible = ref(false)
|
||
const editing = ref<Cat | null>(null)
|
||
const form = ref({ name: '', iconKey: 'tag', type: 'expense' })
|
||
|
||
const iconOptions = [
|
||
{ value: 'food', label: '🍜 餐饮' }, { value: 'cup', label: '🥤 饮品' },
|
||
{ value: 'cart', label: '🛒 购物' }, { value: 'metro', label: '🚇 交通' },
|
||
{ value: 'house', label: '🏠 住房' }, { value: 'game', label: '🎮 娱乐' },
|
||
{ value: 'pill', label: '💊 医疗' }, { value: 'book', label: '📖 学习' },
|
||
{ value: 'shirt', label: '👕 服饰' }, { value: 'gift', label: '🎁 人情' },
|
||
{ value: 'plane', label: '✈️ 旅行' }, { value: 'tag', label: '🏷️ 其他' },
|
||
{ value: 'money', label: '💰 工资' }, { value: 'briefcase', label: '💼 兼职' },
|
||
{ value: 'chart', label: '📈 理财' }, { value: 'card', label: '💳 报销' },
|
||
{ value: 'sparkle', label: '✨ 奖金' },
|
||
]
|
||
|
||
const expenseCats = () => list.value.filter(c => c.type === 'Expense')
|
||
const incomeCats = () => list.value.filter(c => c.type === 'Income')
|
||
|
||
onMounted(refresh)
|
||
async function refresh() { loading.value = true; try { list.value = await api.sysCategories() } finally { loading.value = false } }
|
||
|
||
function openNew() { editing.value = null; form.value = { name: '', iconKey: 'tag', type: 'expense' }; modalVisible.value = true }
|
||
function openEdit(c: Cat) {
|
||
editing.value = c
|
||
form.value = { name: c.name, iconKey: c.iconKey, type: c.type === 'Income' ? 'income' : 'expense' }
|
||
modalVisible.value = true
|
||
}
|
||
|
||
async function save() {
|
||
const d = { name: form.value.name, iconKey: form.value.iconKey, type: form.value.type }
|
||
if (editing.value) await api.updateSysCategory(editing.value.id, d)
|
||
else await api.createSysCategory(d)
|
||
message.success(editing.value ? '已更新' : '已创建')
|
||
modalVisible.value = false
|
||
refresh()
|
||
}
|
||
|
||
async function del(id: number) { await api.deleteSysCategory(id); message.success('已删除(软删)'); refresh() }
|
||
</script>
|
||
|
||
<template>
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
||
<h2>系统默认分类</h2>
|
||
<a-button type="primary" @click="openNew"><PlusOutlined /> 新建分类</a-button>
|
||
</div>
|
||
<p style="color:#999;font-size:12px;margin-bottom:14px">
|
||
此处管理新用户注册时获得的默认分类。删除后已分配用户的分类不受影响(软删除,仅对新用户隐藏)。
|
||
</p>
|
||
|
||
<a-row :gutter="16">
|
||
<a-col :span="12">
|
||
<a-card title="💸 支出分类" size="small">
|
||
<a-table :columns="[
|
||
{ title: '排序', dataIndex: 'sortOrder', width: 55 },
|
||
{ title: '图标', key: 'icon', width: 55 },
|
||
{ title: '名称', dataIndex: 'name', key: 'name' },
|
||
{ title: '', key: 'act', width: 90 },
|
||
]" :dataSource="expenseCats()" :loading="loading" rowKey="id" size="small" :pagination="{ pageSize: 10 }">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'icon'">
|
||
<span style="font-size:18px">{{ iconOptions.find(i=>i.value===record.iconKey)?.label?.split(' ')[0] || '🏷️' }}</span>
|
||
</template>
|
||
<template v-if="column.key === 'act'">
|
||
<a-button size="small" type="link" @click="openEdit(record)"><EditOutlined /></a-button>
|
||
<a-popconfirm title="确定软删除?" @confirm="del(record.id)">
|
||
<a-button size="small" type="link" danger><DeleteOutlined /></a-button>
|
||
</a-popconfirm>
|
||
</template>
|
||
</template>
|
||
</a-table>
|
||
</a-card>
|
||
</a-col>
|
||
<a-col :span="12">
|
||
<a-card title="💰 收入分类" size="small">
|
||
<a-table :columns="[
|
||
{ title: '排序', dataIndex: 'sortOrder', width: 55 },
|
||
{ title: '图标', key: 'icon', width: 55 },
|
||
{ title: '名称', dataIndex: 'name', key: 'name' },
|
||
{ title: '', key: 'act', width: 90 },
|
||
]" :dataSource="incomeCats()" :loading="loading" rowKey="id" size="small" :pagination="{ pageSize: 10 }">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'icon'">
|
||
<span style="font-size:18px">{{ iconOptions.find(i=>i.value===record.iconKey)?.label?.split(' ')[0] || '🏷️' }}</span>
|
||
</template>
|
||
<template v-if="column.key === 'act'">
|
||
<a-button size="small" type="link" @click="openEdit(record)"><EditOutlined /></a-button>
|
||
<a-popconfirm title="确定软删除?" @confirm="del(record.id)">
|
||
<a-button size="small" type="link" danger><DeleteOutlined /></a-button>
|
||
</a-popconfirm>
|
||
</template>
|
||
</template>
|
||
</a-table>
|
||
</a-card>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<a-modal v-model:open="modalVisible" :title="editing ? '编辑分类' : '新建系统分类'" @ok="save" :width="400">
|
||
<a-form layout="vertical" style="margin-top:8px">
|
||
<a-form-item label="分类名称"><a-input v-model:value="form.name" placeholder="如:宠物" /></a-form-item>
|
||
<a-form-item label="图标">
|
||
<a-select v-model:value="form.iconKey" :options="iconOptions" />
|
||
</a-form-item>
|
||
<a-form-item label="类型">
|
||
<a-radio-group v-model:value="form.type">
|
||
<a-radio value="expense">💸 支出</a-radio>
|
||
<a-radio value="income">💰 收入</a-radio>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
</a-form>
|
||
</a-modal>
|
||
</template> |