- 删除应用配置页面及相关组件,重构路由为 /developer/config/[id].vue - 移除开发者文档页面及其导航与样式实现 - 清理开发者侧功能完善工作日志文件 - 删除全局.gitignore配置文件,清理无用忽略规则 - 优化应用配置页面的参数读取和路由结构,解决刷新404问题 - 解决数据库配置唯一键冲突,调整保存逻辑避免重复插入 - 移除对后端配置加密字段的 secret 标记,修正加密异常问题
384 lines
12 KiB
Vue
384 lines
12 KiB
Vue
<script setup lang="ts">
|
||
definePageMeta({ layout: 'admin' })
|
||
|
||
// 财务统计
|
||
const financeStats = ref([
|
||
{ label: '本月收入', value: '128.5', unit: '万', icon: 'fa-arrow-down', gradient: 'from-green-500 to-teal-500', change: '+18%', up: true },
|
||
{ label: '本月支出', value: '89.5', unit: '万', icon: 'fa-arrow-up', gradient: 'from-red-500 to-pink-500', change: '+12%', up: false },
|
||
{ label: '本月利润', value: '39.0', unit: '万', icon: 'fa-chart-line', gradient: 'from-blue-500 to-purple-500', change: '+25%', up: true },
|
||
{ label: '应收账款', value: '256.8', unit: '万', icon: 'fa-wallet', gradient: 'from-orange-500 to-yellow-500', change: '-5%', up: true },
|
||
])
|
||
|
||
// 收支记录
|
||
const transactions = ref([
|
||
{ id: 'TX-2026040901', type: 'income', category: '销售收款', amount: 58000, customer: '深圳市精密机械有限公司', date: '2026-04-09', status: 'completed' },
|
||
{ id: 'TX-2026040802', type: 'expense', category: '采购付款', amount: 36000, supplier: '上海五金工具厂', date: '2026-04-08', status: 'completed' },
|
||
{ id: 'TX-2026040801', type: 'expense', category: '工资支出', amount: 125000, supplier: '人力资源部', date: '2026-04-08', status: 'completed' },
|
||
{ id: 'TX-2026040703', type: 'income', category: '销售收款', amount: 42000, customer: '东莞市金属材料公司', date: '2026-04-07', status: 'completed' },
|
||
{ id: 'TX-2026040702', type: 'expense', category: '水电费', amount: 8500, supplier: '电力公司', date: '2026-04-07', status: 'completed' },
|
||
{ id: 'TX-2026040601', type: 'income', category: '销售收款', amount: 75000, customer: '苏州液压设备厂', date: '2026-04-06', status: 'completed' },
|
||
{ id: 'TX-2026040501', type: 'expense', category: '设备维修', amount: 12000, supplier: '设备维修部', date: '2026-04-05', status: 'completed' },
|
||
{ id: 'TX-2026040502', type: 'expense', category: '办公费用', amount: 3200, supplier: '办公用品供应商', date: '2026-04-05', status: 'completed' },
|
||
])
|
||
|
||
// 应收应付
|
||
const receivables = ref([
|
||
{ customer: '深圳市精密机械有限公司', amount: 85000, dueDate: '2026-04-15', status: 'pending' },
|
||
{ customer: '东莞市金属材料公司', amount: 68000, dueDate: '2026-04-20', status: 'pending' },
|
||
{ customer: '苏州液压设备厂', amount: 42000, dueDate: '2026-04-25', status: 'overdue' },
|
||
{ customer: '广州电子科技有限公司', amount: 95000, dueDate: '2026-05-01', status: 'pending' },
|
||
])
|
||
|
||
const typeFilter = ref('all')
|
||
const searchKeyword = ref('')
|
||
const activeTab = ref('transactions')
|
||
|
||
const filteredTransactions = computed(() => {
|
||
return transactions.value.filter((item) => {
|
||
const matchType = typeFilter.value === 'all' || item.type === typeFilter.value
|
||
const matchSearch = !searchKeyword.value ||
|
||
item.id.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||
item.category.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||
(item.customer && item.customer.toLowerCase().includes(searchKeyword.value.toLowerCase()))
|
||
return matchType && matchSearch
|
||
})
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="finance-page">
|
||
<!-- 页面标题 -->
|
||
<div class="page-header mb-6">
|
||
<div>
|
||
<h2 class="text-2xl font-bold text-gray-800">财务管理</h2>
|
||
<p class="text-gray-500 mt-1">监控收支状况,管理应收账款</p>
|
||
</div>
|
||
<div class="flex gap-3">
|
||
<a-button>
|
||
<template #icon><i class="fas fa-download mr-1"></i></template>
|
||
导出报表
|
||
</a-button>
|
||
<a-button type="primary">
|
||
<template #icon><i class="fas fa-plus mr-1"></i></template>
|
||
记账
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<div class="grid grid-cols-4 gap-6 mb-6">
|
||
<div
|
||
v-for="stat in financeStats"
|
||
:key="stat.label"
|
||
class="glass rounded-2xl p-6 card-hover cursor-pointer"
|
||
>
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div
|
||
class="w-12 h-12 rounded-xl flex items-center justify-center text-white"
|
||
:class="`bg-gradient-to-br ${stat.gradient}`"
|
||
>
|
||
<i :class="`fas ${stat.icon}`"></i>
|
||
</div>
|
||
<span
|
||
class="text-sm font-medium"
|
||
:class="stat.up ? 'text-green-500' : 'text-red-500'"
|
||
>
|
||
<i :class="stat.up ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
|
||
{{ stat.change }}
|
||
</span>
|
||
</div>
|
||
<h3 class="text-3xl font-bold text-gray-800 mb-1">
|
||
<span class="text-base text-gray-500">¥</span>{{ stat.value }}<span class="text-base text-gray-500 ml-1">{{ stat.unit }}</span>
|
||
</h3>
|
||
<p class="text-gray-500 text-sm">{{ stat.label }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab切换 -->
|
||
<div class="glass rounded-2xl p-4 mb-6">
|
||
<a-radio-group v-model:value="activeTab" button-style="solid">
|
||
<a-radio-button value="transactions">
|
||
<i class="fas fa-list mr-1"></i>收支记录
|
||
</a-radio-button>
|
||
<a-radio-button value="receivables">
|
||
<i class="fas fa-hand-holding-usd mr-1"></i>应收账款
|
||
</a-radio-button>
|
||
</a-radio-group>
|
||
</div>
|
||
|
||
<!-- 收支记录 -->
|
||
<div v-show="activeTab === 'transactions'" class="glass rounded-2xl p-6">
|
||
<!-- 筛选 -->
|
||
<div class="flex items-center justify-between mb-4">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-gray-600 font-medium">类型筛选:</span>
|
||
<a-radio-group v-model:value="typeFilter" button-style="solid">
|
||
<a-radio-button value="all">全部</a-radio-button>
|
||
<a-radio-button value="income">收入</a-radio-button>
|
||
<a-radio-button value="expense">支出</a-radio-button>
|
||
</a-radio-group>
|
||
</div>
|
||
<a-input-search
|
||
v-model:value="searchKeyword"
|
||
placeholder="搜索单号、类别、对象..."
|
||
style="width: 280px"
|
||
allow-clear
|
||
/>
|
||
</div>
|
||
|
||
<a-table
|
||
:dataSource="filteredTransactions"
|
||
:pagination="{ pageSize: 10 }"
|
||
rowKey="id"
|
||
:scroll="{ x: 1000 }"
|
||
>
|
||
<a-table-column title="单号" dataIndex="id" width="140" />
|
||
<a-table-column title="类型" dataIndex="type" width="80" align="center">
|
||
<template #default="{ text }">
|
||
<a-tag :color="text === 'income' ? 'success' : 'error'">
|
||
{{ text === 'income' ? '收入' : '支出' }}
|
||
</a-tag>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="类别" dataIndex="category" width="120" />
|
||
<a-table-column title="金额(元)" dataIndex="amount" width="130" align="right">
|
||
<template #default="{ record, text }">
|
||
<span class="font-medium" :class="record.type === 'income' ? 'text-green-600' : 'text-red-600'">
|
||
{{ record.type === 'income' ? '+' : '-' }}¥{{ text.toLocaleString() }}
|
||
</span>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="对方" width="200">
|
||
<template #default="{ record }">
|
||
<span class="font-medium">{{ record.customer || record.supplier || '-' }}</span>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="日期" dataIndex="date" width="120" />
|
||
<a-table-column title="状态" width="100" align="center">
|
||
<template #default>
|
||
<a-tag color="success">已完成</a-tag>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="操作" width="100" align="center" fixed="right">
|
||
<template #default>
|
||
<div class="flex items-center gap-2 justify-center">
|
||
<a-button type="link" size="small" title="详情">
|
||
<i class="fas fa-eye"></i>
|
||
</a-button>
|
||
</div>
|
||
</template>
|
||
</a-table-column>
|
||
</a-table>
|
||
</div>
|
||
|
||
<!-- 应收账款 -->
|
||
<div v-show="activeTab === 'receivables'" class="glass rounded-2xl p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="font-bold text-lg text-gray-800">
|
||
<i class="fas fa-hand-holding-usd text-orange-500 mr-2"></i>
|
||
应收账款列表
|
||
</h3>
|
||
<a-button type="primary">
|
||
<template #icon><i class="fas fa-plus mr-1"></i></template>
|
||
登记收款
|
||
</a-button>
|
||
</div>
|
||
|
||
<a-table
|
||
:dataSource="receivables"
|
||
:pagination="false"
|
||
rowKey="customer"
|
||
>
|
||
<a-table-column title="客户名称" dataIndex="customer">
|
||
<template #default="{ text }">
|
||
<div class="flex items-center gap-2">
|
||
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center text-white text-xs">
|
||
<i class="fas fa-building"></i>
|
||
</div>
|
||
<span class="font-medium">{{ text }}</span>
|
||
</div>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="应收金额(元)" dataIndex="amount" width="150" align="right">
|
||
<template #default="{ text }">
|
||
<span class="font-medium text-orange-600">¥{{ text.toLocaleString() }}</span>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="到期日期" dataIndex="dueDate" width="140" />
|
||
<a-table-column title="状态" dataIndex="status" width="100" align="center">
|
||
<template #default="{ text }">
|
||
<a-tag v-if="text === 'pending'" color="processing">待收款</a-tag>
|
||
<a-tag v-else-if="text === 'overdue'" color="error">已逾期</a-tag>
|
||
<a-tag v-else color="success">已收款</a-tag>
|
||
</template>
|
||
</a-table-column>
|
||
<a-table-column title="操作" width="160" align="center" fixed="right">
|
||
<template #default>
|
||
<div class="flex items-center gap-2 justify-center">
|
||
<a-button type="primary" size="small" ghost>催款</a-button>
|
||
<a-button type="link" size="small">详情</a-button>
|
||
</div>
|
||
</template>
|
||
</a-table-column>
|
||
</a-table>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.finance-page {
|
||
padding: 0;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.glass {
|
||
background: rgba(255, 255, 255, 0.85);
|
||
backdrop-filter: blur(20px);
|
||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.card-hover {
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.card-hover:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.grid {
|
||
display: grid;
|
||
}
|
||
|
||
.grid-cols-4 {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.grid-cols-4 {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
.mb-6 {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.mb-4 {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.mr-1 {
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.mr-2 {
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.ml-1 {
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.text-2xl {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.text-3xl {
|
||
font-size: 30px;
|
||
}
|
||
|
||
.text-lg {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.text-sm {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.text-base {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.rounded-2xl {
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.rounded-lg {
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.p-6 {
|
||
padding: 24px;
|
||
}
|
||
|
||
.p-4 {
|
||
padding: 16px;
|
||
}
|
||
|
||
.gap-6 {
|
||
gap: 24px;
|
||
}
|
||
|
||
.gap-3 {
|
||
gap: 12px;
|
||
}
|
||
|
||
.gap-2 {
|
||
gap: 8px;
|
||
}
|
||
|
||
.flex {
|
||
display: flex;
|
||
}
|
||
|
||
.items-center {
|
||
align-items: center;
|
||
}
|
||
|
||
.justify-between {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.font-bold {
|
||
font-weight: 700;
|
||
}
|
||
|
||
.font-medium {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.text-gray-800 {
|
||
color: #1f2937;
|
||
}
|
||
|
||
.text-gray-600 {
|
||
color: #4b5563;
|
||
}
|
||
|
||
.text-gray-500 {
|
||
color: #6b7280;
|
||
}
|
||
|
||
.text-green-600 {
|
||
color: #16a34a;
|
||
}
|
||
|
||
.text-red-600 {
|
||
color: #dc2626;
|
||
}
|
||
|
||
.text-orange-600 {
|
||
color: #ea580c;
|
||
}
|
||
|
||
.gap-2 {
|
||
gap: 8px;
|
||
}
|
||
</style>
|