892 lines
22 KiB
Plaintext
892 lines
22 KiB
Plaintext
<!-- 养老管理系统 - 财务管理 -->
|
||
<template>
|
||
<view class="finance-management">
|
||
<view class="header">
|
||
<text class="title">财务管理</text>
|
||
<button class="add-btn" @click="showAddBill">添加账单</button>
|
||
</view>
|
||
|
||
<!-- 统计卡片 -->
|
||
<view class="stats-section">
|
||
<view class="stat-card">
|
||
<view class="stat-icon"></view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.total_amount.toFixed(2) }}</text>
|
||
<text class="stat-label">总金额</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">✅</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.paid_amount.toFixed(2) }}</text>
|
||
<text class="stat-label">已收金额</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⏰</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.pending_amount.toFixed(2) }}</text>
|
||
<text class="stat-label">待收金额</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⚠️</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.overdue_amount.toFixed(2) }}</text>
|
||
<text class="stat-label">逾期金额</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 筛选区域 -->
|
||
<view class="filter-section">
|
||
<view class="filter-item">
|
||
<text class="filter-label">老人:</text>
|
||
<picker-view class="picker" :value="selectedElderIndex" @change="onElderChange">
|
||
<picker-view-column>
|
||
<view v-for="(elder, index) in elderOptions" :key="elder.id" class="picker-item">
|
||
{{ elder.name }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">类型:</text>
|
||
<picker-view class="picker" :value="selectedTypeIndex" @change="onTypeChange">
|
||
<picker-view-column>
|
||
<view v-for="(type, index) in typeOptions" :key="index" class="picker-item">
|
||
{{ type.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">状态:</text>
|
||
<picker-view class="picker" :value="selectedStatusIndex" @change="onStatusChange">
|
||
<picker-view-column>
|
||
<view v-for="(status, index) in statusOptions" :key="index" class="picker-item">
|
||
{{ status.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<button class="search-btn" @click="searchBills">搜索</button>
|
||
</view>
|
||
|
||
<!-- 账单列表 -->
|
||
<view class="bills-list">
|
||
<view v-for="bill in bills" :key="bill.id" class="bill-item" @click="viewBillDetail(bill)">
|
||
<view class="bill-header">
|
||
<text class="bill-type">{{ getTypeText(bill.bill_type) }}</text>
|
||
<view class="status-badge" :class="getStatusClass(bill.status)">
|
||
<text class="status-text">{{ getStatusText(bill.status) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="bill-info">
|
||
<text class="elder-name">{{ getElderName(bill.elder_id) }}</text>
|
||
<text class="bill-amount">金额: ¥{{ bill.amount.toFixed(2) }}</text>
|
||
<text class="bill-description">{{ bill.description ?? '无描述' }}</text>
|
||
</view>
|
||
<view class="bill-dates">
|
||
<text class="date-text">到期: {{ formatDate(bill.due_date) }}</text>
|
||
<text class="date-text" v-if="bill.paid_date">支付: {{ formatDate(bill.paid_date) }}</text>
|
||
</view>
|
||
<view class="bill-actions">
|
||
<button class="action-btn edit-btn" @click.stop="editBill(bill)">编辑</button>
|
||
<button class="action-btn pay-btn" v-if="bill.status === 'pending'" @click.stop="markAsPaid(bill)">标记已付</button>
|
||
<button class="action-btn print-btn" @click.stop="printBill(bill)">打印</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 添加/编辑账单弹窗 -->
|
||
<view v-if="showBillModal" class="modal-overlay" @click="closeBillModal">
|
||
<view class="modal-content" @click.stop="">
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ isEditMode ? '编辑账单' : '添加账单' }}</text>
|
||
<button class="close-btn" @click="closeBillModal">×</button>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-group">
|
||
<text class="form-label">老人:</text>
|
||
<picker-view class="form-picker" :value="formData.elderIndex" @change="onFormElderChange">
|
||
<picker-view-column>
|
||
<view v-for="(elder, index) in elderOptions" :key="elder.id" class="picker-item">
|
||
{{ elder.name }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">账单类型:</text>
|
||
<picker-view class="form-picker" :value="formData.typeIndex" @change="onFormTypeChange">
|
||
<picker-view-column>
|
||
<view v-for="(type, index) in billTypes" :key="index" class="picker-item">
|
||
{{ type.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">金额:</text>
|
||
<input class="form-input" v-model="formData.amount" type="number" placeholder="请输入金额" />
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">描述:</text>
|
||
<textarea class="form-textarea" v-model="formData.description" placeholder="请输入账单描述"></textarea>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">到期日期:</text>
|
||
<lime-date-time-picker v-model="formData.due_date" type="date" :placeholder="'选择日期'" class="date-picker" />
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">支付方式:</text>
|
||
<picker-view class="form-picker" :value="formData.paymentMethodIndex" @change="onFormPaymentMethodChange">
|
||
<picker-view-column>
|
||
<view v-for="(method, index) in paymentMethods" :key="index" class="picker-item">
|
||
{{ method.label }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
<view class="form-group">
|
||
<text class="form-label">备注:</text>
|
||
<textarea class="form-textarea" v-model="formData.notes" placeholder="请输入备注"></textarea>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="cancel-btn" @click="closeBillModal">取消</button>
|
||
<button class="save-btn" @click="saveBill">保存</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import type { Bill, Elder } from '../types.uts'
|
||
import { formatDate, getStatusClass } from '../types.uts'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
|
||
// 统计数据类型
|
||
type FinanceStats = {
|
||
total_amount: number
|
||
paid_amount: number
|
||
pending_amount: number
|
||
overdue_amount: number
|
||
}
|
||
|
||
// 响应式数据
|
||
const bills = ref<Bill[]>([])
|
||
const elderOptions = ref<Elder[]>([])
|
||
const eldersMap = ref<Map<string, string>>(new Map())
|
||
const stats = ref<FinanceStats>({
|
||
total_amount: 0,
|
||
paid_amount: 0,
|
||
pending_amount: 0,
|
||
overdue_amount: 0
|
||
})
|
||
|
||
// 筛选相关
|
||
const selectedElderIndex = ref([0])
|
||
const selectedTypeIndex = ref([0])
|
||
const selectedStatusIndex = ref([0])
|
||
|
||
const typeOptions = [
|
||
{ value: 'all', label: '全部类型' },
|
||
{ value: 'monthly_fee', label: '月费' },
|
||
{ value: 'medical', label: '医疗费' },
|
||
{ value: 'nursing', label: '护理费' },
|
||
{ value: 'meal', label: '餐费' },
|
||
{ value: 'activity', label: '活动费' },
|
||
{ value: 'other', label: '其他费用' }
|
||
]
|
||
|
||
const statusOptions = [
|
||
{ value: 'all', label: '全部状态' },
|
||
{ value: 'pending', label: '待支付' },
|
||
{ value: 'paid', label: '已支付' },
|
||
{ value: 'overdue', label: '已逾期' },
|
||
{ value: 'cancelled', label: '已取消' }
|
||
]
|
||
|
||
const billTypes = [
|
||
{ value: 'monthly_fee', label: '月费' },
|
||
{ value: 'medical', label: '医疗费' },
|
||
{ value: 'nursing', label: '护理费' },
|
||
{ value: 'meal', label: '餐费' },
|
||
{ value: 'activity', label: '活动费' },
|
||
{ value: 'other', label: '其他费用' }
|
||
]
|
||
|
||
const paymentMethods = [
|
||
{ value: 'cash', label: '现金' },
|
||
{ value: 'card', label: '银行卡' },
|
||
{ value: 'transfer', label: '转账' },
|
||
{ value: 'wechat', label: '微信支付' },
|
||
{ value: 'alipay', label: '支付宝' }
|
||
]
|
||
|
||
// 弹窗相关
|
||
const showBillModal = ref(false)
|
||
const isEditMode = ref(false)
|
||
const currentBillId = ref<string | null>(null)
|
||
|
||
// 表单数据
|
||
const formData = ref({
|
||
elderIndex: [0],
|
||
typeIndex: [0],
|
||
amount: '',
|
||
description: '',
|
||
due_date: '',
|
||
paymentMethodIndex: [0],
|
||
notes: ''
|
||
})
|
||
|
||
// 页面加载
|
||
onLoad(() => {
|
||
loadData()
|
||
})
|
||
|
||
// 加载数据
|
||
async function loadData(): Promise<void> {
|
||
try {
|
||
await Promise.all([
|
||
loadElders(),
|
||
loadBills(),
|
||
loadStats()
|
||
])
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error)
|
||
uni.showToast({
|
||
title: '加载数据失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
|
||
// 加载老人列表
|
||
async function loadElders(): Promise<void> {
|
||
const result = await supa.executeAs<Elder>('eldercare_admin', `
|
||
SELECT id, name FROM ec_elders
|
||
WHERE status = 'active'
|
||
ORDER BY name
|
||
`)
|
||
elderOptions.value = [{ id: '', name: '全部老人' } as Elder, ...result]
|
||
|
||
// 建立映射
|
||
const map = new Map<string, string>()
|
||
for (let i: Int = 0; i < result.length; i++) {
|
||
const elder = result[i]
|
||
map.set(elder.id, elder.name)
|
||
}
|
||
eldersMap.value = map
|
||
}
|
||
|
||
// 加载账单列表
|
||
async function loadBills(): Promise<void> {
|
||
let whereClause = "WHERE 1=1"
|
||
|
||
// 老人筛选
|
||
if (selectedElderIndex.value[0] > 0) {
|
||
const selectedElder = elderOptions.value[selectedElderIndex.value[0]]
|
||
whereClause += ` AND elder_id = '${selectedElder.id}'`
|
||
}
|
||
|
||
// 类型筛选
|
||
if (selectedTypeIndex.value[0] > 0) {
|
||
const selectedType = typeOptions[selectedTypeIndex.value[0]]
|
||
whereClause += ` AND bill_type = '${selectedType.value}'`
|
||
}
|
||
|
||
// 状态筛选
|
||
if (selectedStatusIndex.value[0] > 0) {
|
||
const selectedStatus = statusOptions[selectedStatusIndex.value[0]]
|
||
whereClause += ` AND status = '${selectedStatus.value}'`
|
||
}
|
||
|
||
const result = await supa.executeAs<Bill>('eldercare_admin', `
|
||
SELECT * FROM ec_bills
|
||
${whereClause}
|
||
ORDER BY created_at DESC
|
||
`)
|
||
bills.value = result
|
||
}
|
||
|
||
// 加载统计数据
|
||
async function loadStats(): Promise<void> {
|
||
const result = await supa.executeAs<FinanceStats>('eldercare_admin', `
|
||
SELECT
|
||
COALESCE(SUM(amount), 0) as total_amount,
|
||
COALESCE(SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END), 0) as paid_amount,
|
||
COALESCE(SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END), 0) as pending_amount,
|
||
COALESCE(SUM(CASE WHEN status = 'overdue' THEN amount ELSE 0 END), 0) as overdue_amount
|
||
FROM ec_bills
|
||
`)
|
||
|
||
if (result.length > 0) {
|
||
stats.value = result[0]
|
||
}
|
||
}
|
||
|
||
// 获取老人姓名
|
||
function getElderName(elderId: string): string {
|
||
return eldersMap.value.get(elderId) ?? '未知老人'
|
||
}
|
||
|
||
// 获取类型文本
|
||
function getTypeText(type: string): string {
|
||
const typeMap: Record<string, string> = {
|
||
'monthly_fee': '月费',
|
||
'medical': '医疗费',
|
||
'nursing': '护理费',
|
||
'meal': '餐费',
|
||
'activity': '活动费',
|
||
'other': '其他费用'
|
||
}
|
||
return typeMap[type] ?? type
|
||
}
|
||
|
||
// 获取状态文本
|
||
function getStatusText(status: string): string {
|
||
const statusMap: Record<string, string> = {
|
||
'pending': '待支付',
|
||
'paid': '已支付',
|
||
'overdue': '已逾期',
|
||
'cancelled': '已取消'
|
||
}
|
||
return statusMap[status] ?? status
|
||
}
|
||
|
||
// 筛选事件
|
||
function onElderChange(e: any): void {
|
||
selectedElderIndex.value = e.detail.value
|
||
}
|
||
|
||
function onTypeChange(e: any): void {
|
||
selectedTypeIndex.value = e.detail.value
|
||
}
|
||
|
||
function onStatusChange(e: any): void {
|
||
selectedStatusIndex.value = e.detail.value
|
||
}
|
||
|
||
// 搜索账单
|
||
function searchBills(): void {
|
||
loadBills()
|
||
loadStats()
|
||
}
|
||
|
||
// 查看账单详情
|
||
function viewBillDetail(bill: Bill): void {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/finance/detail?id=${bill.id}`
|
||
})
|
||
}
|
||
|
||
// 编辑账单
|
||
function editBill(bill: Bill): void {
|
||
isEditMode.value = true
|
||
currentBillId.value = bill.id
|
||
|
||
// 填充表单数据
|
||
const elderIndex = elderOptions.value.findIndex(elder => elder.id === bill.elder_id)
|
||
const typeIndex = billTypes.findIndex(type => type.value === bill.bill_type)
|
||
const paymentMethodIndex = paymentMethods.findIndex(method => method.value === bill.payment_method)
|
||
|
||
formData.value = {
|
||
elderIndex: [elderIndex > 0 ? elderIndex : 0],
|
||
typeIndex: [typeIndex > 0 ? typeIndex : 0],
|
||
amount: bill.amount.toString(),
|
||
description: bill.description ?? '',
|
||
due_date: bill.due_date ?? '',
|
||
paymentMethodIndex: [paymentMethodIndex > 0 ? paymentMethodIndex : 0],
|
||
notes: bill.notes ?? ''
|
||
}
|
||
|
||
showBillModal.value = true
|
||
}
|
||
|
||
// 标记为已支付
|
||
async function markAsPaid(bill: Bill): Promise<void> {
|
||
uni.showModal({
|
||
title: '确认支付',
|
||
content: '确定标记此账单为已支付吗?',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
await supa.executeAs('eldercare_admin', `
|
||
UPDATE ec_bills
|
||
SET status = 'paid', paid_date = CURRENT_DATE, updated_at = NOW()
|
||
WHERE id = '${bill.id}'
|
||
`)
|
||
|
||
uni.showToast({
|
||
title: '支付成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
loadBills()
|
||
loadStats()
|
||
} catch (error) {
|
||
console.error('标记支付失败:', error)
|
||
uni.showToast({
|
||
title: '操作失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 打印账单
|
||
function printBill(bill: Bill): void {
|
||
// 这里可以实现打印功能
|
||
uni.showToast({
|
||
title: '打印功能待实现',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
// 显示添加账单弹窗
|
||
function showAddBill(): void {
|
||
isEditMode.value = false
|
||
currentBillId.value = null
|
||
|
||
// 重置表单
|
||
const today = new Date()
|
||
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1, today.getDate())
|
||
|
||
formData.value = {
|
||
elderIndex: [0],
|
||
typeIndex: [0],
|
||
amount: '',
|
||
description: '',
|
||
due_date: formatDate(nextMonth.toISOString()),
|
||
paymentMethodIndex: [0],
|
||
notes: ''
|
||
}
|
||
|
||
showBillModal.value = true
|
||
}
|
||
|
||
// 关闭弹窗
|
||
function closeBillModal(): void {
|
||
showBillModal.value = false
|
||
}
|
||
|
||
// 表单事件
|
||
function onFormElderChange(e: any): void {
|
||
formData.value.elderIndex = e.detail.value
|
||
}
|
||
|
||
function onFormTypeChange(e: any): void {
|
||
formData.value.typeIndex = e.detail.value
|
||
}
|
||
|
||
function onFormPaymentMethodChange(e: any): void {
|
||
formData.value.paymentMethodIndex = e.detail.value
|
||
}
|
||
|
||
function onDueDateChange(date: string): void {
|
||
formData.value.due_date = date
|
||
}
|
||
|
||
// 保存账单
|
||
async function saveBill(): Promise<void> {
|
||
// 验证表单
|
||
if (formData.value.elderIndex[0] === 0) {
|
||
uni.showToast({
|
||
title: '请选择老人',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
if (formData.value.amount.trim() === '' || parseFloat(formData.value.amount) <= 0) {
|
||
uni.showToast({
|
||
title: '请输入有效金额',
|
||
icon: 'error'
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
const selectedElder = elderOptions.value[formData.value.elderIndex[0]]
|
||
const selectedType = billTypes[formData.value.typeIndex[0]]
|
||
const selectedPaymentMethod = paymentMethods[formData.value.paymentMethodIndex[0]]
|
||
|
||
if (isEditMode.value && currentBillId.value !== null) {
|
||
// 更新账单
|
||
await supa.executeAs('eldercare_admin', `
|
||
UPDATE ec_bills SET
|
||
elder_id = '${selectedElder.id}',
|
||
bill_type = '${selectedType.value}',
|
||
amount = ${formData.value.amount},
|
||
description = '${formData.value.description}',
|
||
due_date = ${formData.value.due_date ? `'${formData.value.due_date}'` : 'NULL'},
|
||
payment_method = '${selectedPaymentMethod.value}',
|
||
notes = '${formData.value.notes}',
|
||
updated_at = NOW()
|
||
WHERE id = '${currentBillId.value}'
|
||
`)
|
||
} else {
|
||
// 新增账单
|
||
await supa.executeAs('eldercare_admin', `
|
||
INSERT INTO ec_bills (
|
||
elder_id, bill_type, amount, description, due_date,
|
||
payment_method, notes, status
|
||
) VALUES (
|
||
'${selectedElder.id}',
|
||
'${selectedType.value}',
|
||
${formData.value.amount},
|
||
'${formData.value.description}',
|
||
${formData.value.due_date ? `'${formData.value.due_date}'` : 'NULL'},
|
||
'${selectedPaymentMethod.value}',
|
||
'${formData.value.notes}',
|
||
'pending'
|
||
)
|
||
`)
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '保存成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
closeBillModal()
|
||
loadBills()
|
||
loadStats()
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
uni.showToast({
|
||
title: '保存失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* uts-android 兼容性重构:
|
||
1. 移除所有嵌套选择器、伪类(如 :last-child、&.xxx),全部 class 扁平化。
|
||
2. 所有间距用 margin-right/margin-bottom 控制,禁止 gap、flex-wrap、嵌套。
|
||
3. 所有布局 display: flex,禁止 grid、gap、伪类。
|
||
4. 组件间距、分隔线全部用 border/margin 控制。
|
||
5. 新增.is-last、.is-active、.is-overdue 等辅助 class。
|
||
*/
|
||
.finance-management {
|
||
padding: 20px;
|
||
background: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
.header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
.title {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
.add-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
border: 1px solid #2196f3;
|
||
background-color: #2196f3;
|
||
color: white;
|
||
}
|
||
.stats-section {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-bottom: 20px;
|
||
}
|
||
.stat-card {
|
||
flex: 1;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
margin-right: 15px;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
.stat-card.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.stat-icon {
|
||
font-size: 24px;
|
||
margin-right: 10px;
|
||
}
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
.stat-number {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
}
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
.filter-section {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.filter-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-right: 15px;
|
||
}
|
||
.filter-item.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.filter-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-right: 8px;
|
||
}
|
||
.picker {
|
||
width: 150px;
|
||
}
|
||
.search-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 8px;
|
||
background: #2196f3;
|
||
color: white;
|
||
border: none;
|
||
}
|
||
.bills-list {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
min-height: 300px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.bill-item {
|
||
padding: 16px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.bill-item.is-last {
|
||
border-bottom: none;
|
||
}
|
||
.bill-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.bill-type {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-right: 10px;
|
||
}
|
||
.status-badge {
|
||
padding: 4px 10px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
background-color: #f5f5f5;
|
||
color: #999;
|
||
}
|
||
.status-badge.paid {
|
||
background-color: #f6ffed;
|
||
color: #52c41a;
|
||
}
|
||
.status-badge.pending {
|
||
background-color: #fffbe6;
|
||
color: #faad14;
|
||
}
|
||
.status-badge.overdue {
|
||
background-color: #fff2f0;
|
||
color: #ff4d4f;
|
||
}
|
||
.status-text {
|
||
font-weight: 500;
|
||
}
|
||
.bill-info {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.elder-name {
|
||
font-size: 14px;
|
||
color: #1890ff;
|
||
margin-right: 10px;
|
||
}
|
||
.bill-amount {
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-right: 10px;
|
||
}
|
||
.bill-description {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
.bill-dates {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.date-text {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-right: 10px;
|
||
}
|
||
.bill-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
.action-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 15px;
|
||
border: none;
|
||
font-size: 12px;
|
||
color: white;
|
||
margin-right: 8px;
|
||
}
|
||
.edit-btn {
|
||
background-color: #1890ff;
|
||
}
|
||
.pay-btn {
|
||
background-color: #52c41a;
|
||
}
|
||
.print-btn {
|
||
background-color: #faad14;
|
||
color: #fff;
|
||
}
|
||
.action-btn.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.3);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
.modal-content {
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
width: 500px;
|
||
max-width: 95vw;
|
||
}
|
||
.modal-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 24px;
|
||
color: #999;
|
||
}
|
||
.modal-body {
|
||
margin-bottom: 16px;
|
||
}
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
.form-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-right: 8px;
|
||
width: 80px;
|
||
flex-shrink: 0;
|
||
}
|
||
.form-picker {
|
||
width: 150px;
|
||
}
|
||
.form-input {
|
||
flex: 1;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
padding: 8px 12px;
|
||
font-size: 14px;
|
||
}
|
||
.form-textarea {
|
||
flex: 1;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
padding: 8px 12px;
|
||
font-size: 14px;
|
||
min-height: 60px;
|
||
}
|
||
.form-date-picker {
|
||
width: 150px;
|
||
}
|
||
.modal-footer {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
}
|
||
.cancel-btn {
|
||
background-color: #f5f5f5;
|
||
color: #666;
|
||
margin-right: 10px;
|
||
}
|
||
.save-btn {
|
||
background-color: #2196f3;
|
||
color: white;
|
||
}
|
||
@media (max-width: 768px) {
|
||
.finance-management {
|
||
padding: 15px;
|
||
}
|
||
.stats-section {
|
||
flex-direction: column;
|
||
}
|
||
.stat-card {
|
||
margin: 5px 0;
|
||
min-width: auto;
|
||
}
|
||
.filter-section {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
.filter-item {
|
||
margin-right: 0;
|
||
justify-content: space-between;
|
||
}
|
||
.picker {
|
||
width: 150px;
|
||
}
|
||
.modal-content {
|
||
width: 95%;
|
||
}
|
||
}
|
||
</style>
|