refactor: remove all asset/account functionality (models, schemas, routers, store, views, components, tests, docs)
This commit is contained in:
@@ -24,7 +24,7 @@ const authStore = useAuthStore()
|
||||
// 路由变化时同步 currentView
|
||||
watch(() => route.meta.view, (view) => {
|
||||
if (view) {
|
||||
uiStore.setCurrentView(view as 'list' | 'calendar' | 'quadrant' | 'profile' | 'settings' | 'habits' | 'anniversaries' | 'assets')
|
||||
uiStore.setCurrentView(view as 'list' | 'calendar' | 'quadrant' | 'profile' | 'settings' | 'habits' | 'anniversaries')
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
@@ -89,7 +89,7 @@ onMounted(async () => {
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div v-if="uiStore.currentView !== 'settings' && uiStore.currentView !== 'profile' && uiStore.currentView !== 'habits' && uiStore.currentView !== 'anniversaries' && uiStore.currentView !== 'assets'" class="fab-container">
|
||||
<div v-if="uiStore.currentView !== 'settings' && uiStore.currentView !== 'profile' && uiStore.currentView !== 'habits' && uiStore.currentView !== 'anniversaries'" class="fab-container">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { get, post, put, del, patch } from './request'
|
||||
import type {
|
||||
FinancialAccount, AccountFormData, BalanceUpdateData,
|
||||
AccountHistoryResponse, DebtInstallment, DebtInstallmentFormData
|
||||
} from './types'
|
||||
|
||||
export interface GetAccountHistoryParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export const accountApi = {
|
||||
// ============ 账户 CRUD ============
|
||||
getAccounts(): Promise<FinancialAccount[]> {
|
||||
return get<FinancialAccount[]>('/accounts')
|
||||
},
|
||||
|
||||
getAccount(id: number): Promise<FinancialAccount> {
|
||||
return get<FinancialAccount>(`/accounts/${id}`)
|
||||
},
|
||||
|
||||
createAccount(data: AccountFormData): Promise<FinancialAccount> {
|
||||
return post<FinancialAccount>('/accounts', data)
|
||||
},
|
||||
|
||||
updateAccount(id: number, data: Partial<AccountFormData>): Promise<FinancialAccount> {
|
||||
return put<FinancialAccount>(`/accounts/${id}`, data)
|
||||
},
|
||||
|
||||
deleteAccount(id: number): Promise<{ success: boolean; message?: string }> {
|
||||
return del<{ success: boolean; message?: string }>(`/accounts/${id}`)
|
||||
},
|
||||
|
||||
// ============ 余额操作 ============
|
||||
updateBalance(id: number, data: BalanceUpdateData): Promise<FinancialAccount> {
|
||||
return post<FinancialAccount>(`/accounts/${id}/balance`, data)
|
||||
},
|
||||
|
||||
// ============ 变更历史 ============
|
||||
getHistory(id: number, params?: GetAccountHistoryParams): Promise<AccountHistoryResponse> {
|
||||
return get<AccountHistoryResponse>(`/accounts/${id}/history`, { params })
|
||||
},
|
||||
|
||||
// ============ 分期计划 ============
|
||||
getInstallments(): Promise<DebtInstallment[]> {
|
||||
return get<DebtInstallment[]>('/debt-installments')
|
||||
},
|
||||
|
||||
createInstallment(data: DebtInstallmentFormData): Promise<DebtInstallment> {
|
||||
return post<DebtInstallment>('/debt-installments', data)
|
||||
},
|
||||
|
||||
updateInstallment(id: number, data: Partial<DebtInstallmentFormData>): Promise<DebtInstallment> {
|
||||
return put<DebtInstallment>(`/debt-installments/${id}`, data)
|
||||
},
|
||||
|
||||
deleteInstallment(id: number): Promise<{ success: boolean; message?: string }> {
|
||||
return del<{ success: boolean; message?: string }>(`/debt-installments/${id}`)
|
||||
},
|
||||
|
||||
payInstallment(id: number): Promise<DebtInstallment> {
|
||||
return patch<DebtInstallment>(`/debt-installments/${id}/pay`)
|
||||
},
|
||||
}
|
||||
@@ -186,93 +186,4 @@ export interface AnniversaryFormData {
|
||||
remind_days_before: number
|
||||
}
|
||||
|
||||
// ============ 资产账户相关 ============
|
||||
|
||||
export type AccountType = 'savings' | 'debt'
|
||||
|
||||
export interface FinancialAccount {
|
||||
id: number
|
||||
name: string
|
||||
account_type: AccountType
|
||||
balance: number
|
||||
icon: string
|
||||
color: string
|
||||
sort_order: number
|
||||
is_active: boolean
|
||||
description?: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
installments?: InstallmentInfo[]
|
||||
}
|
||||
|
||||
export interface AccountFormData {
|
||||
name: string
|
||||
account_type: AccountType
|
||||
balance: number
|
||||
icon: string
|
||||
color: string
|
||||
sort_order: number
|
||||
is_active: boolean
|
||||
description?: string | null
|
||||
}
|
||||
|
||||
export interface BalanceUpdateData {
|
||||
new_balance: number
|
||||
note?: string | null
|
||||
}
|
||||
|
||||
export interface InstallmentInfo {
|
||||
next_payment_date: string | null
|
||||
days_until_payment: number | null
|
||||
remaining_periods: number
|
||||
}
|
||||
|
||||
export interface AccountHistoryRecord {
|
||||
id: number
|
||||
account_id: number
|
||||
change_amount: number
|
||||
balance_before: number
|
||||
balance_after: number
|
||||
note?: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface AccountHistoryResponse {
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
records: AccountHistoryRecord[]
|
||||
}
|
||||
|
||||
// ============ 分期还款计划相关 ============
|
||||
|
||||
export interface DebtInstallment {
|
||||
id: number
|
||||
account_id: number
|
||||
total_amount: number
|
||||
total_periods: number
|
||||
current_period: number
|
||||
payment_day: number
|
||||
payment_amount: number
|
||||
start_date: string
|
||||
is_completed: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
next_payment_date: string | null
|
||||
days_until_payment: number | null
|
||||
remaining_periods: number | null
|
||||
account_name: string | null
|
||||
account_icon: string | null
|
||||
account_color: string | null
|
||||
}
|
||||
|
||||
export interface DebtInstallmentFormData {
|
||||
account_id: number
|
||||
total_amount: number
|
||||
total_periods: number
|
||||
current_period: number
|
||||
payment_day: number
|
||||
payment_amount: number
|
||||
start_date: string
|
||||
is_completed: boolean
|
||||
}
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useAccountStore } from '@/stores/useAccountStore'
|
||||
import type { FinancialAccount } from '@/api/types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
editAccount?: FinancialAccount | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
editAccount: null
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
}>()
|
||||
|
||||
const store = useAccountStore()
|
||||
|
||||
const isEdit = computed(() => !!props.editAccount)
|
||||
const dialogTitle = computed(() => isEdit.value ? '编辑账户' : '新建账户')
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
account_type: 'savings' as 'savings' | 'debt',
|
||||
balance: 0,
|
||||
icon: 'wallet',
|
||||
color: '#FFB7C5',
|
||||
is_active: true,
|
||||
description: ''
|
||||
})
|
||||
|
||||
const iconOptions = [
|
||||
{ label: '钱包', value: 'wallet' },
|
||||
{ label: '微信', value: 'wechat' },
|
||||
{ label: '支付宝', value: 'alipay' },
|
||||
{ label: '银行卡', value: 'bank' },
|
||||
{ label: '信用卡', value: 'credit-card' },
|
||||
{ label: '花呗', value: 'huabei' },
|
||||
{ label: '白条', value: 'baitiao' },
|
||||
{ label: '现金', value: 'cash' },
|
||||
{ label: '投资', value: 'investment' },
|
||||
{ label: '其他', value: 'other' },
|
||||
]
|
||||
|
||||
const colorOptions = [
|
||||
'#FFB7C5', '#C8A2C8', '#98D8C8', '#FFB347',
|
||||
'#87CEEB', '#FF6B6B', '#A8E6CF', '#DDA0DD',
|
||||
'#F0E68C', '#20B2AA', '#FF8C69', '#9370DB',
|
||||
]
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
if (props.editAccount) {
|
||||
const a = props.editAccount
|
||||
form.value = {
|
||||
name: a.name,
|
||||
account_type: a.account_type,
|
||||
balance: a.balance,
|
||||
icon: a.icon,
|
||||
color: a.color,
|
||||
is_active: a.is_active,
|
||||
description: a.description || ''
|
||||
}
|
||||
} else {
|
||||
form.value = {
|
||||
name: '',
|
||||
account_type: 'savings',
|
||||
balance: 0,
|
||||
icon: 'wallet',
|
||||
color: '#FFB7C5',
|
||||
is_active: true,
|
||||
description: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSave() {
|
||||
if (!form.value.name.trim()) {
|
||||
ElMessage.warning('请输入账户名称~')
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: form.value.name.trim(),
|
||||
account_type: form.value.account_type,
|
||||
balance: form.value.balance,
|
||||
icon: form.value.icon,
|
||||
color: form.value.color,
|
||||
sort_order: 0,
|
||||
is_active: form.value.is_active,
|
||||
description: form.value.description.trim() || null
|
||||
}
|
||||
|
||||
if (isEdit.value && props.editAccount) {
|
||||
const result = await store.updateAccount(props.editAccount.id, data)
|
||||
if (result) ElMessage.success('账户更新成功~')
|
||||
} else {
|
||||
const result = await store.createAccount(data)
|
||||
if (result) ElMessage.success('账户创建成功~')
|
||||
}
|
||||
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
width="480px"
|
||||
@close="handleClose"
|
||||
class="account-dialog"
|
||||
>
|
||||
<div class="form-content">
|
||||
<div class="form-item">
|
||||
<label class="form-label">账户名称</label>
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="如:微信、支付宝、花呗、招商银行..."
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">账户类型</label>
|
||||
<div class="type-switch">
|
||||
<button
|
||||
class="type-btn"
|
||||
:class="{ active: form.account_type === 'savings' }"
|
||||
@click="form.account_type = 'savings'"
|
||||
>
|
||||
<el-icon><Wallet /></el-icon>
|
||||
<span>存款</span>
|
||||
</button>
|
||||
<button
|
||||
class="type-btn"
|
||||
:class="{ active: form.account_type === 'debt' }"
|
||||
@click="form.account_type = 'debt'"
|
||||
>
|
||||
<el-icon><CreditCard /></el-icon>
|
||||
<span>欠款</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">
|
||||
当前余额
|
||||
<span class="form-hint">({{ form.account_type === 'savings' ? '存款金额' : '欠款金额' }})</span>
|
||||
</label>
|
||||
<el-input-number
|
||||
v-model="form.balance"
|
||||
:precision="2"
|
||||
:step="100"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">图标</label>
|
||||
<div class="icon-grid">
|
||||
<button
|
||||
v-for="opt in iconOptions"
|
||||
:key="opt.value"
|
||||
class="icon-btn"
|
||||
:class="{ active: form.icon === opt.value }"
|
||||
@click="form.icon = opt.value"
|
||||
:title="opt.label"
|
||||
>
|
||||
<el-icon :size="18">
|
||||
<Wallet v-if="opt.value === 'wallet'" />
|
||||
<ChatDotRound v-else-if="opt.value === 'wechat'" />
|
||||
<ShoppingCart v-else-if="opt.value === 'alipay'" />
|
||||
<CreditCard v-else-if="opt.value === 'bank' || opt.value === 'credit-card' || opt.value === 'huabei'" />
|
||||
<Ticket v-else-if="opt.value === 'baitiao'" />
|
||||
<Money v-else-if="opt.value === 'cash'" />
|
||||
<TrendCharts v-else-if="opt.value === 'investment'" />
|
||||
<MoreFilled v-else />
|
||||
</el-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">主题色</label>
|
||||
<div class="color-grid">
|
||||
<button
|
||||
v-for="color in colorOptions"
|
||||
:key="color"
|
||||
class="color-btn"
|
||||
:class="{ active: form.color === color }"
|
||||
:style="{ background: color }"
|
||||
@click="form.color = color"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">备注</label>
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选的备注信息"
|
||||
maxlength="500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="isEdit" class="form-item">
|
||||
<label class="form-label">启用状态</label>
|
||||
<el-switch v-model="form.is_active" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">
|
||||
{{ isEdit ? '保存' : '创建' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-content {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
|
||||
.form-hint {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type-switch {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
.type-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border: 2px solid rgba(255, 183, 197, 0.2);
|
||||
border-radius: var(--radius-md);
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--primary);
|
||||
background: rgba(255, 183, 197, 0.1);
|
||||
color: var(--primary);
|
||||
box-shadow: 0 2px 8px rgba(255, 183, 197, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.icon-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid rgba(255, 183, 197, 0.15);
|
||||
border-radius: var(--radius-sm);
|
||||
background: white;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--primary);
|
||||
background: rgba(255, 183, 197, 0.1);
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color-grid {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.color-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--text-primary);
|
||||
box-shadow: 0 0 0 2px white, 0 0 0 4px var(--text-primary);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,235 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useAccountStore } from '@/stores/useAccountStore'
|
||||
import type { FinancialAccount, AccountHistoryRecord } from '@/api/types'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
account: FinancialAccount | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
account: null
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
}>()
|
||||
|
||||
const store = useAccountStore()
|
||||
|
||||
const records = ref<AccountHistoryRecord[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const loading = ref(false)
|
||||
|
||||
async function fetchHistory() {
|
||||
if (!props.account) return
|
||||
loading.value = true
|
||||
const result = await store.fetchHistory(props.account.id, page.value, pageSize)
|
||||
records.value = result.records
|
||||
total.value = result.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val && props.account) {
|
||||
page.value = 1
|
||||
fetchHistory()
|
||||
}
|
||||
})
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
page.value = newPage
|
||||
fetchHistory()
|
||||
}
|
||||
|
||||
function formatAmount(amount: number): string {
|
||||
if (amount > 0) return `+${amount.toFixed(2)}`
|
||||
return amount.toFixed(2)
|
||||
}
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
const d = new Date(dateStr)
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="account ? `${account.name} - 变更历史` : '变更历史'"
|
||||
width="600px"
|
||||
@close="handleClose"
|
||||
class="history-dialog"
|
||||
>
|
||||
<div class="history-content">
|
||||
<div v-if="loading" class="loading-state">
|
||||
<el-icon class="is-loading" :size="24"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="records.length === 0" class="empty-state">
|
||||
<el-icon :size="40" color="#C8A2C8"><Document /></el-icon>
|
||||
<p>暂无变更记录</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="history-list">
|
||||
<div
|
||||
v-for="record in records"
|
||||
:key="record.id"
|
||||
class="history-item"
|
||||
>
|
||||
<div class="history-left">
|
||||
<div
|
||||
class="amount-badge"
|
||||
:class="{ positive: record.change_amount > 0, negative: record.change_amount < 0 }"
|
||||
>
|
||||
<el-icon v-if="record.change_amount > 0"><Top /></el-icon>
|
||||
<el-icon v-else-if="record.change_amount < 0"><Bottom /></el-icon>
|
||||
<el-icon v-else><Minus /></el-icon>
|
||||
<span>{{ formatAmount(record.change_amount) }}</span>
|
||||
</div>
|
||||
<div class="history-info">
|
||||
<span class="history-note">{{ record.note || '未备注' }}</span>
|
||||
<span class="history-date">{{ formatDate(record.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="history-right">
|
||||
<span class="balance-change">
|
||||
{{ record.balance_before.toFixed(2) }}
|
||||
<el-icon :size="12"><Right /></el-icon>
|
||||
{{ record.balance_after.toFixed(2) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="total > pageSize" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="prev, pager, next"
|
||||
small
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.history-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 40px 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 40px 0;
|
||||
color: var(--text-secondary);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.history-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px dashed rgba(255, 183, 197, 0.15);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.history-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.amount-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
|
||||
&.positive {
|
||||
color: #67C23A;
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
&.negative {
|
||||
color: #F56C6C;
|
||||
background: rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.history-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.history-note {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.history-date {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.history-right {
|
||||
.balance-change {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -95,14 +95,6 @@ const currentRouteName = computed(() => route.name as string)
|
||||
<el-icon><Cherry /></el-icon>
|
||||
<span>纪念日</span>
|
||||
</button>
|
||||
<button
|
||||
class="nav-item"
|
||||
:class="{ active: currentRouteName === 'assets' }"
|
||||
@click="router.push('/assets')"
|
||||
>
|
||||
<el-icon><Wallet /></el-icon>
|
||||
<span>资产</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="header-right">
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useAccountStore } from '@/stores/useAccountStore'
|
||||
import type { FinancialAccount } from '@/api/types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
account: FinancialAccount | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
account: null
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
}>()
|
||||
|
||||
const store = useAccountStore()
|
||||
|
||||
const newBalance = ref(0)
|
||||
const note = ref('')
|
||||
const saving = ref(false)
|
||||
|
||||
const changeAmount = computed(() => {
|
||||
if (!props.account) return 0
|
||||
return Math.round((newBalance.value - props.account.balance) * 100) / 100
|
||||
})
|
||||
|
||||
const changeText = computed(() => {
|
||||
const diff = changeAmount.value
|
||||
if (diff > 0) return `+${diff.toFixed(2)}`
|
||||
if (diff < 0) return diff.toFixed(2)
|
||||
return '0.00'
|
||||
})
|
||||
|
||||
const isPositive = computed(() => changeAmount.value >= 0)
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val && props.account) {
|
||||
newBalance.value = props.account.balance
|
||||
note.value = ''
|
||||
saving.value = false
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSave() {
|
||||
if (!props.account) return
|
||||
saving.value = true
|
||||
try {
|
||||
const result = await store.updateBalance(props.account.id, {
|
||||
new_balance: newBalance.value,
|
||||
note: note.value.trim() || null
|
||||
})
|
||||
if (result) {
|
||||
ElMessage.success('余额更新成功~')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
title="更新余额"
|
||||
width="420px"
|
||||
@close="handleClose"
|
||||
class="balance-dialog"
|
||||
>
|
||||
<div class="form-content">
|
||||
<div v-if="account" class="balance-preview">
|
||||
<span class="current-label">{{ account.name }}</span>
|
||||
<span class="current-balance">{{ account.balance.toFixed(2) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="change-indicator" :class="{ positive: isPositive, negative: !isPositive }">
|
||||
<el-icon><Top v-if="isPositive" /><Bottom v-else /></el-icon>
|
||||
<span>{{ changeText }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">新余额</label>
|
||||
<el-input-number
|
||||
v-model="newBalance"
|
||||
:precision="2"
|
||||
:step="100"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">备注</label>
|
||||
<el-input
|
||||
v-model="note"
|
||||
placeholder="如:工资到账、还花呗、日常消费..."
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave" :loading="saving">
|
||||
确认更新
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-content {
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.balance-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: rgba(255, 183, 197, 0.08);
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
.current-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.current-balance {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.change-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
||||
&.positive {
|
||||
color: #67C23A;
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
}
|
||||
|
||||
&.negative {
|
||||
color: #F56C6C;
|
||||
background: rgba(245, 108, 108, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,272 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useAccountStore } from '@/stores/useAccountStore'
|
||||
import type { DebtInstallment, FinancialAccount } from '@/api/types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
editInstallment?: DebtInstallment | null
|
||||
accountId?: number | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
editInstallment: null,
|
||||
accountId: null
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
}>()
|
||||
|
||||
const store = useAccountStore()
|
||||
|
||||
const isEdit = computed(() => !!props.editInstallment)
|
||||
const dialogTitle = computed(() => isEdit.value ? '编辑分期计划' : '新建分期计划')
|
||||
|
||||
const form = ref({
|
||||
account_id: null as number | null,
|
||||
total_amount: 0,
|
||||
total_periods: 3,
|
||||
current_period: 1,
|
||||
payment_day: 12,
|
||||
payment_amount: 0,
|
||||
start_date: '',
|
||||
is_completed: false
|
||||
})
|
||||
|
||||
const debtAccounts = computed(() => store.debtAccounts)
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
if (props.editInstallment) {
|
||||
const inst = props.editInstallment
|
||||
form.value = {
|
||||
account_id: inst.account_id,
|
||||
total_amount: inst.total_amount,
|
||||
total_periods: inst.total_periods,
|
||||
current_period: inst.current_period,
|
||||
payment_day: inst.payment_day,
|
||||
payment_amount: inst.payment_amount,
|
||||
start_date: inst.start_date,
|
||||
is_completed: inst.is_completed
|
||||
}
|
||||
} else {
|
||||
form.value = {
|
||||
account_id: props.accountId || (debtAccounts.value.length > 0 ? debtAccounts.value[0].id : null),
|
||||
total_amount: 0,
|
||||
total_periods: 3,
|
||||
current_period: 1,
|
||||
payment_day: 12,
|
||||
payment_amount: 0,
|
||||
start_date: '',
|
||||
is_completed: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch([() => form.value.total_amount, () => form.value.total_periods], ([amount, periods]) => {
|
||||
if (!isEdit.value && amount > 0 && periods > 0) {
|
||||
form.value.payment_amount = Math.round((amount / periods) * 100) / 100
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSave() {
|
||||
if (!form.value.account_id) {
|
||||
ElMessage.warning('请选择关联的欠款账户~')
|
||||
return
|
||||
}
|
||||
if (form.value.total_amount <= 0) {
|
||||
ElMessage.warning('请输入分期总额~')
|
||||
return
|
||||
}
|
||||
if (form.value.payment_amount <= 0) {
|
||||
ElMessage.warning('请输入每期还款金额~')
|
||||
return
|
||||
}
|
||||
if (!form.value.start_date) {
|
||||
ElMessage.warning('请选择首次还款日期~')
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
account_id: form.value.account_id,
|
||||
total_amount: form.value.total_amount,
|
||||
total_periods: form.value.total_periods,
|
||||
current_period: form.value.current_period,
|
||||
payment_day: form.value.payment_day,
|
||||
payment_amount: form.value.payment_amount,
|
||||
start_date: form.value.start_date,
|
||||
is_completed: form.value.is_completed
|
||||
}
|
||||
|
||||
if (isEdit.value && props.editInstallment) {
|
||||
const result = await store.updateInstallment(props.editInstallment.id, data)
|
||||
if (result) ElMessage.success('分期计划更新成功~')
|
||||
} else {
|
||||
const result = await store.createInstallment(data)
|
||||
if (result) ElMessage.success('分期计划创建成功~')
|
||||
}
|
||||
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
width="460px"
|
||||
@close="handleClose"
|
||||
class="installment-dialog"
|
||||
>
|
||||
<div class="form-content">
|
||||
<div class="form-item">
|
||||
<label class="form-label">关联账户</label>
|
||||
<el-select
|
||||
v-model="form.account_id"
|
||||
placeholder="选择欠款账户"
|
||||
style="width: 100%"
|
||||
:disabled="isEdit"
|
||||
>
|
||||
<el-option
|
||||
v-for="acc in debtAccounts"
|
||||
:key="acc.id"
|
||||
:label="acc.name"
|
||||
:value="acc.id"
|
||||
/>
|
||||
</el-select>
|
||||
<div v-if="debtAccounts.length === 0" class="form-hint" style="margin-top: 8px;">
|
||||
暂无欠款账户,请先创建一个欠款类型的账户~
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">分期总额</label>
|
||||
<el-input-number
|
||||
v-model="form.total_amount"
|
||||
:precision="2"
|
||||
:step="500"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-item form-row">
|
||||
<div class="form-col">
|
||||
<label class="form-label">总期数</label>
|
||||
<el-input-number
|
||||
v-model="form.total_periods"
|
||||
:min="1"
|
||||
:max="36"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<label class="form-label">每月还款日</label>
|
||||
<el-input-number
|
||||
v-model="form.payment_day"
|
||||
:min="1"
|
||||
:max="31"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">每期还款金额</label>
|
||||
<el-input-number
|
||||
v-model="form.payment_amount"
|
||||
:precision="2"
|
||||
:step="100"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div v-if="!isEdit && form.total_amount > 0 && form.total_periods > 0" class="form-hint" style="margin-top: 8px;">
|
||||
自动计算: {{ form.total_amount.toFixed(2) }} / {{ form.total_periods }} = {{ (form.total_amount / form.total_periods).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isEdit" class="form-item">
|
||||
<label class="form-label">当前期数(第几期待还)</label>
|
||||
<el-input-number
|
||||
v-model="form.current_period"
|
||||
:min="1"
|
||||
:max="form.total_periods"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label class="form-label">首次还款日期</label>
|
||||
<el-date-picker
|
||||
v-model="form.start_date"
|
||||
type="date"
|
||||
:locale="zhCn"
|
||||
placeholder="选择首次还款日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
:clearable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">
|
||||
{{ isEdit ? '保存' : '创建' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-content {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
.form-col {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -49,12 +49,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/AnniversaryPage.vue'),
|
||||
meta: { title: '纪念日', view: 'anniversaries' }
|
||||
},
|
||||
{
|
||||
path: '/assets',
|
||||
name: 'assets',
|
||||
component: () => import('@/views/AssetPage.vue'),
|
||||
meta: { title: '资产总览', view: 'assets' }
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { accountApi } from '@/api/accounts'
|
||||
import type {
|
||||
FinancialAccount, AccountFormData, BalanceUpdateData,
|
||||
AccountHistoryRecord, DebtInstallment, DebtInstallmentFormData
|
||||
} from '@/api/types'
|
||||
|
||||
export const useAccountStore = defineStore('account', () => {
|
||||
const accounts = ref<FinancialAccount[]>([])
|
||||
const installments = ref<DebtInstallment[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
const savingsAccounts = computed(() =>
|
||||
accounts.value.filter(a => a.account_type === 'savings' && a.is_active)
|
||||
)
|
||||
|
||||
const debtAccounts = computed(() =>
|
||||
accounts.value.filter(a => a.account_type === 'debt' && a.is_active)
|
||||
)
|
||||
|
||||
const totalSavings = computed(() =>
|
||||
savingsAccounts.value.reduce((sum, a) => sum + a.balance, 0)
|
||||
)
|
||||
|
||||
const totalDebt = computed(() =>
|
||||
debtAccounts.value.reduce((sum, a) => sum + a.balance, 0)
|
||||
)
|
||||
|
||||
const netAssets = computed(() => totalSavings.value - totalDebt.value)
|
||||
|
||||
const activeInstallments = computed(() =>
|
||||
installments.value.filter(i => !i.is_completed && i.days_until_payment !== null)
|
||||
)
|
||||
|
||||
const upcomingPayments = computed(() =>
|
||||
activeInstallments.value
|
||||
.filter(i => i.days_until_payment! >= 0)
|
||||
.sort((a, b) => a.days_until_payment! - b.days_until_payment!)
|
||||
)
|
||||
|
||||
// ============ 账户操作 ============
|
||||
|
||||
async function fetchAccounts() {
|
||||
loading.value = true
|
||||
try {
|
||||
accounts.value = await accountApi.getAccounts()
|
||||
} catch (error) {
|
||||
console.error('获取账户列表失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createAccount(data: AccountFormData): Promise<FinancialAccount | null> {
|
||||
try {
|
||||
const account = await accountApi.createAccount(data)
|
||||
accounts.value.push(account)
|
||||
return account
|
||||
} catch (error) {
|
||||
console.error('创建账户失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAccount(id: number, data: Partial<AccountFormData>): Promise<FinancialAccount | null> {
|
||||
try {
|
||||
const updated = await accountApi.updateAccount(id, data)
|
||||
const index = accounts.value.findIndex(a => a.id === id)
|
||||
if (index !== -1) {
|
||||
accounts.value[index] = updated
|
||||
}
|
||||
return updated
|
||||
} catch (error) {
|
||||
console.error('更新账户失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAccount(id: number): Promise<boolean> {
|
||||
try {
|
||||
await accountApi.deleteAccount(id)
|
||||
accounts.value = accounts.value.filter(a => a.id !== id)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('删除账户失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateBalance(id: number, data: BalanceUpdateData): Promise<FinancialAccount | null> {
|
||||
try {
|
||||
const updated = await accountApi.updateBalance(id, data)
|
||||
const index = accounts.value.findIndex(a => a.id === id)
|
||||
if (index !== -1) {
|
||||
accounts.value[index] = updated
|
||||
}
|
||||
return updated
|
||||
} catch (error) {
|
||||
console.error('更新余额失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchHistory(id: number, page = 1, pageSize = 20): Promise<AccountHistoryResponse> {
|
||||
try {
|
||||
return await accountApi.getHistory(id, { page, page_size: pageSize })
|
||||
} catch (error) {
|
||||
console.error('获取变更历史失败:', error)
|
||||
return { total: 0, page: 1, page_size: pageSize, records: [] }
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 分期计划操作 ============
|
||||
|
||||
async function fetchInstallments() {
|
||||
try {
|
||||
installments.value = await accountApi.getInstallments()
|
||||
} catch (error) {
|
||||
console.error('获取分期计划失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function createInstallment(data: DebtInstallmentFormData): Promise<DebtInstallment | null> {
|
||||
try {
|
||||
const inst = await accountApi.createInstallment(data)
|
||||
installments.value.push(inst)
|
||||
installments.value.sort((a, b) => {
|
||||
const aActive = !a.is_completed && a.days_until_payment !== null ? 0 : 1
|
||||
const bActive = !b.is_completed && b.days_until_payment !== null ? 0 : 1
|
||||
if (aActive !== bActive) return aActive - bActive
|
||||
return (a.days_until_payment ?? 9999) - (b.days_until_payment ?? 9999)
|
||||
})
|
||||
return inst
|
||||
} catch (error) {
|
||||
console.error('创建分期计划失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function updateInstallment(id: number, data: Partial<DebtInstallmentFormData>): Promise<DebtInstallment | null> {
|
||||
try {
|
||||
const updated = await accountApi.updateInstallment(id, data)
|
||||
const index = installments.value.findIndex(i => i.id === id)
|
||||
if (index !== -1) {
|
||||
installments.value[index] = updated
|
||||
}
|
||||
return updated
|
||||
} catch (error) {
|
||||
console.error('更新分期计划失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteInstallment(id: number): Promise<boolean> {
|
||||
try {
|
||||
await accountApi.deleteInstallment(id)
|
||||
installments.value = installments.value.filter(i => i.id !== id)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('删除分期计划失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function payInstallment(id: number): Promise<DebtInstallment | null> {
|
||||
try {
|
||||
const updated = await accountApi.payInstallment(id)
|
||||
const index = installments.value.findIndex(i => i.id === id)
|
||||
if (index !== -1) {
|
||||
installments.value[index] = updated
|
||||
}
|
||||
return updated
|
||||
} catch (error) {
|
||||
console.error('标记还款失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await Promise.all([fetchAccounts(), fetchInstallments()])
|
||||
}
|
||||
|
||||
return {
|
||||
accounts,
|
||||
installments,
|
||||
loading,
|
||||
savingsAccounts,
|
||||
debtAccounts,
|
||||
totalSavings,
|
||||
totalDebt,
|
||||
netAssets,
|
||||
activeInstallments,
|
||||
upcomingPayments,
|
||||
fetchAccounts,
|
||||
createAccount,
|
||||
updateAccount,
|
||||
deleteAccount,
|
||||
updateBalance,
|
||||
fetchHistory,
|
||||
fetchInstallments,
|
||||
createInstallment,
|
||||
updateInstallment,
|
||||
deleteInstallment,
|
||||
payInstallment,
|
||||
init,
|
||||
}
|
||||
})
|
||||
@@ -12,7 +12,7 @@ export const useUIStore = defineStore('ui', () => {
|
||||
const editingCategory = ref<Category | null>(null)
|
||||
const sidebarCollapsed = ref(false)
|
||||
const globalLoading = ref(false)
|
||||
const currentView = ref<'list' | 'calendar' | 'quadrant' | 'profile' | 'settings' | 'habits' | 'anniversaries' | 'assets'>('list')
|
||||
const currentView = ref<'list' | 'calendar' | 'quadrant' | 'profile' | 'settings' | 'habits' | 'anniversaries'>('list')
|
||||
const calendarMode = ref<'week' | 'monthly'>('monthly')
|
||||
|
||||
function openTaskDialog(task?: Task) {
|
||||
@@ -43,7 +43,7 @@ export const useUIStore = defineStore('ui', () => {
|
||||
globalLoading.value = loading
|
||||
}
|
||||
|
||||
function setCurrentView(view: 'list' | 'calendar' | 'quadrant' | 'profile' | 'settings' | 'habits' | 'anniversaries' | 'assets') {
|
||||
function setCurrentView(view: 'list' | 'calendar' | 'quadrant' | 'profile' | 'settings' | 'habits' | 'anniversaries') {
|
||||
currentView.value = view
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user