feat: 添加 /api/review-stats 端点和仪表盘实时数据集成
This commit is contained in:
487
server/README.md
Normal file
487
server/README.md
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
# BillAI 服务器 API 文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
BillAI 服务器是一个基于 Go 的后端服务,用于处理账单的上传、清洗、分析和存储。提供 REST API 接口供前端调用。
|
||||||
|
|
||||||
|
**服务地址**: `http://localhost:8080`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 接口列表
|
||||||
|
|
||||||
|
### 1. 健康检查
|
||||||
|
|
||||||
|
**端点**: `GET /health`
|
||||||
|
|
||||||
|
**功能**: 检查服务器健康状态
|
||||||
|
|
||||||
|
**参数**: 无
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 账单管理 API
|
||||||
|
|
||||||
|
### 2. 上传账单文件
|
||||||
|
|
||||||
|
**端点**: `POST /api/upload`
|
||||||
|
|
||||||
|
**功能**: 上传支付宝或微信账单文件进行清洗和分析
|
||||||
|
|
||||||
|
**请求方式**: `multipart/form-data`
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 说明 |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| file | File | ✓ | 账单文件(CSV格式) |
|
||||||
|
| type | string | ✓ | 账单类型:`alipay` 或 `wechat` |
|
||||||
|
| format | string | - | 输出格式:`csv` 或 `json`(默认: csv) |
|
||||||
|
| year | number | - | 账单年份(用于数据验证) |
|
||||||
|
| month | number | - | 账单月份(用于数据验证) |
|
||||||
|
|
||||||
|
**响应示例** (成功):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"message": "账单处理完成",
|
||||||
|
"data": {
|
||||||
|
"bill_type": "alipay",
|
||||||
|
"raw_count": 50,
|
||||||
|
"cleaned_count": 48,
|
||||||
|
"duplicate_count": 2,
|
||||||
|
"needs_review_count": 5,
|
||||||
|
"file_name": "20260110_150405_alipay_1.csv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例** (全部重复):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"message": "文件中的 50 条记录全部已存在,无需重复导入",
|
||||||
|
"data": {
|
||||||
|
"bill_type": "alipay",
|
||||||
|
"raw_count": 0,
|
||||||
|
"cleaned_count": 0,
|
||||||
|
"duplicate_count": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例** (失败):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": false,
|
||||||
|
"message": "账单类型无效,仅支持 alipay 或 wechat"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 获取账单列表
|
||||||
|
|
||||||
|
**端点**: `GET /api/bills`
|
||||||
|
|
||||||
|
**功能**: 获取清洗后的账单数据,支持分页、筛选和排序
|
||||||
|
|
||||||
|
**请求参数** (Query):
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 说明 |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| page | number | - | 页码(从1开始,默认: 1) |
|
||||||
|
| page_size | number | - | 每页数量(默认: 20,最大: 100) |
|
||||||
|
| start_date | string | - | 开始日期(YYYY-MM-DD 格式) |
|
||||||
|
| end_date | string | - | 结束日期(YYYY-MM-DD 格式) |
|
||||||
|
| category | string | - | 交易分类筛选 |
|
||||||
|
| type | string | - | 账单来源:`alipay`、`wechat` 或 `manual` |
|
||||||
|
| income_expense | string | - | 收支类型:`收入` 或 `支出` |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"data": {
|
||||||
|
"total": 150,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 20,
|
||||||
|
"pages": 8,
|
||||||
|
"total_expense": 5250.50,
|
||||||
|
"total_income": 8000.00,
|
||||||
|
"bills": [
|
||||||
|
{
|
||||||
|
"_id": "507f1f77bcf86cd799439011",
|
||||||
|
"bill_type": "alipay",
|
||||||
|
"transaction_id": "2021123456789",
|
||||||
|
"merchant_order_no": "2021123456",
|
||||||
|
"time": "2026-01-10T10:30:00Z",
|
||||||
|
"category": "餐饮美食",
|
||||||
|
"merchant": "星巴克",
|
||||||
|
"description": "咖啡",
|
||||||
|
"income_expense": "支出",
|
||||||
|
"amount": 28.00,
|
||||||
|
"pay_method": "支付宝",
|
||||||
|
"status": "交易成功",
|
||||||
|
"remark": "员工补贴",
|
||||||
|
"review_level": "",
|
||||||
|
"created_at": "2026-01-10T10:30:00Z",
|
||||||
|
"updated_at": "2026-01-10T10:30:00Z",
|
||||||
|
"source_file": "20260110_150405_alipay_1.csv",
|
||||||
|
"upload_batch": "20260110_150405"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": false,
|
||||||
|
"message": "数据库未连接"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 手动创建账单
|
||||||
|
|
||||||
|
**端点**: `POST /api/bills/manual`
|
||||||
|
|
||||||
|
**功能**: 手动添加单条或批量账单,支持去重
|
||||||
|
|
||||||
|
**请求方式**: `application/json`
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"bills": [
|
||||||
|
{
|
||||||
|
"time": "2026-01-10 14:30:00",
|
||||||
|
"category": "餐饮美食",
|
||||||
|
"merchant": "测试餐厅",
|
||||||
|
"description": "午餐",
|
||||||
|
"income_expense": "支出",
|
||||||
|
"amount": 50.00,
|
||||||
|
"pay_method": "支付宝",
|
||||||
|
"status": "交易成功",
|
||||||
|
"remark": "测试账单"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**请求参数说明**:
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 说明 |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| bills | Array | ✓ | 账单数组 |
|
||||||
|
| bills[].time | string | ✓ | 交易时间(格式: `YYYY-MM-DD HH:mm:ss`) |
|
||||||
|
| bills[].category | string | ✓ | 交易分类 |
|
||||||
|
| bills[].income_expense | string | ✓ | 收支类型:`收入` 或 `支出` |
|
||||||
|
| bills[].amount | number | ✓ | 金额(>0) |
|
||||||
|
| bills[].merchant | string | - | 交易对方(可选) |
|
||||||
|
| bills[].description | string | - | 商品说明 |
|
||||||
|
| bills[].pay_method | string | - | 支付方式 |
|
||||||
|
| bills[].status | string | - | 交易状态(默认: `交易成功`) |
|
||||||
|
| bills[].remark | string | - | 备注 |
|
||||||
|
|
||||||
|
**响应示例** (成功):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"message": "创建成功",
|
||||||
|
"data": {
|
||||||
|
"success": 1,
|
||||||
|
"failed": 0,
|
||||||
|
"duplicates": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例** (部分失败):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"message": "创建成功",
|
||||||
|
"data": {
|
||||||
|
"success": 3,
|
||||||
|
"failed": 1,
|
||||||
|
"duplicates": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": false,
|
||||||
|
"message": "时间格式错误: 2026-01-10"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 获取需要复核的记录
|
||||||
|
|
||||||
|
**端点**: `GET /api/review`
|
||||||
|
|
||||||
|
**功能**: 获取清洗过程中标记为需要人工复核的记录
|
||||||
|
|
||||||
|
**请求参数** (Query):
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必需 | 说明 |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| file | string | ✓ | 输出文件名(来自上传接口返回值) |
|
||||||
|
| page | number | - | 页码(默认: 1) |
|
||||||
|
| page_size | number | - | 每页数量(默认: 20) |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"data": {
|
||||||
|
"total": 5,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 20,
|
||||||
|
"high_count": 2,
|
||||||
|
"low_count": 3,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"row": 15,
|
||||||
|
"time": "2026-01-10 10:30:00",
|
||||||
|
"merchant": "未知商户",
|
||||||
|
"amount": 100.00,
|
||||||
|
"category": "未分类",
|
||||||
|
"review_level": "HIGH",
|
||||||
|
"issue": "无法识别的商户名称",
|
||||||
|
"suggestion": "请手动确认或修改商户名称"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 获取月度统计数据
|
||||||
|
|
||||||
|
**端点**: `GET /api/monthly-stats`
|
||||||
|
|
||||||
|
**功能**: 获取每个月的收入、支出统计(不受日期筛选影响,返回全部月份数据)
|
||||||
|
|
||||||
|
**请求参数**: 无
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": true,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"month": "2025-12",
|
||||||
|
"expense": 8234.50,
|
||||||
|
"income": 15000.00
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-01",
|
||||||
|
"expense": 5250.50,
|
||||||
|
"income": 8000.00
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": false,
|
||||||
|
"message": "数据库未连接"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据模型
|
||||||
|
|
||||||
|
### 账单数据 (CleanedBill)
|
||||||
|
|
||||||
|
```go
|
||||||
|
type CleanedBill struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
|
||||||
|
BillType string `bson:"bill_type" json:"bill_type"` // alipay/wechat/manual
|
||||||
|
TransactionID string `bson:"transaction_id" json:"transaction_id"`
|
||||||
|
MerchantOrderNo string `bson:"merchant_order_no" json:"merchant_order_no"`
|
||||||
|
Time time.Time `bson:"time" json:"time"`
|
||||||
|
Category string `bson:"category" json:"category"`
|
||||||
|
Merchant string `bson:"merchant" json:"merchant"`
|
||||||
|
Description string `bson:"description" json:"description"`
|
||||||
|
IncomeExpense string `bson:"income_expense" json:"income_expense"` // 收入/支出
|
||||||
|
Amount float64 `bson:"amount" json:"amount"`
|
||||||
|
PayMethod string `bson:"pay_method" json:"pay_method"`
|
||||||
|
Status string `bson:"status" json:"status"`
|
||||||
|
Remark string `bson:"remark" json:"remark"`
|
||||||
|
ReviewLevel string `bson:"review_level" json:"review_level"` // HIGH/LOW/""
|
||||||
|
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `bson:"updated_at" json:"updated_at"`
|
||||||
|
SourceFile string `bson:"source_file" json:"source_file"`
|
||||||
|
UploadBatch string `bson:"upload_batch" json:"upload_batch"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 月度统计 (MonthlyStat)
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MonthlyStat struct {
|
||||||
|
Month string `bson:"month" json:"month"` // YYYY-MM
|
||||||
|
Expense float64 `bson:"expense" json:"expense"` // 支出总额
|
||||||
|
Income float64 `bson:"income" json:"income"` // 收入总额
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
所有 API 响应都遵循以下格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": boolean,
|
||||||
|
"message": "错误或成功消息",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 常见错误码
|
||||||
|
|
||||||
|
| HTTP 状态码 | result | 说明 |
|
||||||
|
|-----------|--------|------|
|
||||||
|
| 200 | true | 请求成功 |
|
||||||
|
| 200 | false | 业务逻辑错误(如参数不合法) |
|
||||||
|
| 400 | false | 请求参数错误 |
|
||||||
|
| 404 | false | 资源不存在 |
|
||||||
|
| 500 | false | 服务器内部错误 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 时区说明
|
||||||
|
|
||||||
|
- **存储**: 所有时间在数据库中按 **UTC 时区** 存储(ISO 8601 格式)
|
||||||
|
- **输入**: 手动添加账单时,时间按 **本地时区** 解析(使用 `time.ParseInLocation`)
|
||||||
|
- **输出**: API 返回的时间均为 ISO 8601 UTC 格式
|
||||||
|
|
||||||
|
### 时间格式
|
||||||
|
|
||||||
|
- 手动创建账单时: `YYYY-MM-DD HH:mm:ss`(本地时间)
|
||||||
|
- 日期筛选参数: `YYYY-MM-DD`(本地日期)
|
||||||
|
- API 返回时间: ISO 8601 UTC 格式(如 `2026-01-10T10:30:00Z`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 上传账单文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/upload \
|
||||||
|
-F "file=@statement.csv" \
|
||||||
|
-F "type=alipay" \
|
||||||
|
-F "format=csv"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询账单列表
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8080/api/bills?page=1&page_size=20&start_date=2026-01-01&end_date=2026-01-10&type=alipay"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动添加账单
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/bills/manual \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"bills": [
|
||||||
|
{
|
||||||
|
"time": "2026-01-10 14:30:00",
|
||||||
|
"category": "餐饮美食",
|
||||||
|
"merchant": "星巴克",
|
||||||
|
"income_expense": "支出",
|
||||||
|
"amount": 28.00,
|
||||||
|
"pay_method": "支付宝"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取月度统计
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/api/monthly-stats
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
服务器配置文件位于 `config.yaml`,主要配置项:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
port: "8080"
|
||||||
|
|
||||||
|
mongodb:
|
||||||
|
uri: "mongodb://admin:password@localhost:27017"
|
||||||
|
database: "billai"
|
||||||
|
collections:
|
||||||
|
raw: "bills_raw"
|
||||||
|
cleaned: "bills_cleaned"
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
mode: "http" # http 或 process
|
||||||
|
url: "http://analyzer:8001"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发信息
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── main.go # 程序入口
|
||||||
|
├── router/ # 路由配置
|
||||||
|
├── handler/ # 请求处理器
|
||||||
|
├── service/ # 业务逻辑
|
||||||
|
├── repository/ # 数据访问层
|
||||||
|
├── database/ # 数据库连接
|
||||||
|
├── model/ # 数据模型
|
||||||
|
├── adapter/ # 外部服务适配器
|
||||||
|
├── config/ # 配置管理
|
||||||
|
└── README.md # 本文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 主要依赖
|
||||||
|
|
||||||
|
- **Gin**: Web 框架
|
||||||
|
- **MongoDB Driver**: 数据库驱动
|
||||||
|
- **Go 1.21**: 运行环境
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 版本历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 说明 |
|
||||||
|
|-----|------|------|
|
||||||
|
| 1.0.0 | 2026-01-10 | 初始版本,支持账单上传、查询、手动添加和统计 |
|
||||||
|
|
||||||
@@ -198,3 +198,43 @@ func MonthlyStats(c *gin.Context) {
|
|||||||
Data: stats,
|
Data: stats,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReviewStats 获取待复核数据统计
|
||||||
|
func ReviewStats(c *gin.Context) {
|
||||||
|
repo := repository.GetRepository()
|
||||||
|
|
||||||
|
// 从MongoDB查询所有需要复核的账单
|
||||||
|
bills, err := repo.GetBillsNeedReview()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.ReviewResponse{
|
||||||
|
Result: false,
|
||||||
|
Message: "查询失败: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
highCount := 0
|
||||||
|
lowCount := 0
|
||||||
|
totalCount := 0
|
||||||
|
|
||||||
|
// 统计各等级的复核记录
|
||||||
|
for _, bill := range bills {
|
||||||
|
totalCount++
|
||||||
|
if bill.ReviewLevel == "HIGH" {
|
||||||
|
highCount++
|
||||||
|
} else if bill.ReviewLevel == "LOW" {
|
||||||
|
lowCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, model.ReviewResponse{
|
||||||
|
Result: true,
|
||||||
|
Message: "获取成功",
|
||||||
|
Data: &model.ReviewData{
|
||||||
|
Total: totalCount,
|
||||||
|
High: highCount,
|
||||||
|
Low: lowCount,
|
||||||
|
Records: nil, // 统计接口不返回具体记录
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,5 +55,8 @@ func setupAPIRoutes(r *gin.Engine) {
|
|||||||
|
|
||||||
// 月度统计(全部数据)
|
// 月度统计(全部数据)
|
||||||
api.GET("/monthly-stats", handler.MonthlyStats)
|
api.GET("/monthly-stats", handler.MonthlyStats)
|
||||||
|
|
||||||
|
// 待复核数据统计
|
||||||
|
api.GET("/review-stats", handler.ReviewStats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,3 +318,14 @@ export async function createManualBills(bills: ManualBillInput[]): Promise<Creat
|
|||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取待复核数据统计
|
||||||
|
export async function fetchReviewStats(): Promise<ReviewResponse> {
|
||||||
|
const response = await fetch(`${API_BASE}/api/review-stats`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { uploadBill, type UploadResponse, type BillType } from '$lib/api';
|
import { onMount } from 'svelte';
|
||||||
|
import { uploadBill, fetchMonthlyStats, fetchReviewStats, fetchBills, type UploadResponse, type BillType } from '$lib/api';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { Badge } from '$lib/components/ui/badge';
|
import { Badge } from '$lib/components/ui/badge';
|
||||||
@@ -23,38 +24,146 @@
|
|||||||
let uploadResult: UploadResponse | null = $state(null);
|
let uploadResult: UploadResponse | null = $state(null);
|
||||||
let errorMessage = $state('');
|
let errorMessage = $state('');
|
||||||
|
|
||||||
// 模拟统计数据
|
// 实时统计数据
|
||||||
const stats = [
|
let stats = $state([
|
||||||
{
|
{
|
||||||
title: '本月支出',
|
title: '本月支出',
|
||||||
value: '¥12,580.00',
|
value: '¥0.00',
|
||||||
change: '+12.5%',
|
change: '+0%',
|
||||||
trend: 'up',
|
trend: 'up' as const,
|
||||||
description: '较上月增加'
|
description: '加载中...'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '本月收入',
|
title: '本月收入',
|
||||||
value: '¥25,000.00',
|
value: '¥0.00',
|
||||||
change: '+8.2%',
|
change: '+0%',
|
||||||
trend: 'up',
|
trend: 'up' as const,
|
||||||
description: '较上月增加'
|
description: '加载中...'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '待复核',
|
title: '待复核',
|
||||||
value: '23',
|
value: '0',
|
||||||
change: '-15%',
|
change: '+0%',
|
||||||
trend: 'down',
|
trend: 'up' as const,
|
||||||
description: '需要人工确认'
|
description: '需要人工确认'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '已处理账单',
|
title: '已处理账单',
|
||||||
value: '156',
|
value: '0',
|
||||||
change: '+25%',
|
change: '+0%',
|
||||||
trend: 'up',
|
trend: 'up' as const,
|
||||||
|
description: '累计处理记录'
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 加载真实统计数据
|
||||||
|
async function loadStats() {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// 计算上月
|
||||||
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||||
|
const previousMonth = `${lastMonth.getFullYear()}-${String(lastMonth.getMonth() + 1).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
console.log('Current month:', currentMonth);
|
||||||
|
console.log('Previous month:', previousMonth);
|
||||||
|
|
||||||
|
// 获取月度统计数据
|
||||||
|
const monthlyResponse = await fetchMonthlyStats();
|
||||||
|
console.log('Monthly response:', monthlyResponse);
|
||||||
|
const monthlyStats = monthlyResponse.data || [];
|
||||||
|
console.log('Monthly stats:', monthlyStats);
|
||||||
|
|
||||||
|
// 获取待复核统计
|
||||||
|
const reviewResponse = await fetchReviewStats();
|
||||||
|
console.log('Review response:', reviewResponse);
|
||||||
|
const reviewTotal = reviewResponse.data?.total || 0;
|
||||||
|
console.log('Review total:', reviewTotal);
|
||||||
|
|
||||||
|
// 获取已处理账单数量
|
||||||
|
const billsResponse = await fetchBills({ page_size: 1 });
|
||||||
|
console.log('Bills response:', billsResponse);
|
||||||
|
const billTotal = billsResponse.data?.total || 0;
|
||||||
|
console.log('Bill total:', billTotal);
|
||||||
|
|
||||||
|
// 提取当月和上月的数据
|
||||||
|
const currentData = monthlyStats.find(m => m.month === currentMonth);
|
||||||
|
const previousData = monthlyStats.find(m => m.month === previousMonth);
|
||||||
|
|
||||||
|
console.log('Current data:', currentData);
|
||||||
|
console.log('Previous data:', previousData);
|
||||||
|
|
||||||
|
// 计算支出变化百分比
|
||||||
|
const currentExpense = currentData?.expense || 0;
|
||||||
|
const previousExpense = previousData?.expense || 0;
|
||||||
|
const expenseChange = previousExpense > 0
|
||||||
|
? ((currentExpense - previousExpense) / previousExpense * 100).toFixed(1)
|
||||||
|
: 0;
|
||||||
|
const expenseTrend = parseFloat(expenseChange.toString()) >= 0 ? 'up' : 'down';
|
||||||
|
|
||||||
|
// 计算收入变化百分比
|
||||||
|
const currentIncome = currentData?.income || 0;
|
||||||
|
const previousIncome = previousData?.income || 0;
|
||||||
|
const incomeChange = previousIncome > 0
|
||||||
|
? ((currentIncome - previousIncome) / previousIncome * 100).toFixed(1)
|
||||||
|
: 0;
|
||||||
|
const incomeTrend = parseFloat(incomeChange.toString()) >= 0 ? 'up' : 'down';
|
||||||
|
|
||||||
|
// 格式化金额
|
||||||
|
const formatAmount = (amount: number) => {
|
||||||
|
return `¥${amount.toFixed(2)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatChange = (change: number | string) => {
|
||||||
|
const changeNum = typeof change === 'string' ? parseFloat(change) : change;
|
||||||
|
const sign = changeNum >= 0 ? '+' : '';
|
||||||
|
return `${sign}${changeNum.toFixed(1)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStats = [
|
||||||
|
{
|
||||||
|
title: '本月支出',
|
||||||
|
value: formatAmount(currentExpense),
|
||||||
|
change: formatChange(expenseChange),
|
||||||
|
trend: expenseTrend,
|
||||||
|
description: '较上月' + (expenseTrend === 'up' ? '增加' : '减少')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '本月收入',
|
||||||
|
value: formatAmount(currentIncome),
|
||||||
|
change: formatChange(incomeChange),
|
||||||
|
trend: incomeTrend,
|
||||||
|
description: '较上月' + (incomeTrend === 'up' ? '增加' : '减少')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '待复核',
|
||||||
|
value: reviewTotal.toString(),
|
||||||
|
change: '+0%',
|
||||||
|
trend: 'up' as const,
|
||||||
|
description: '需要人工确认'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '已处理账单',
|
||||||
|
value: billTotal.toString(),
|
||||||
|
change: '+0%',
|
||||||
|
trend: 'up' as const,
|
||||||
description: '累计处理记录'
|
description: '累计处理记录'
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
console.log('New stats:', newStats);
|
||||||
|
stats = newStats;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load stats:', err);
|
||||||
|
// 保持默认状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
loadStats();
|
||||||
|
});
|
||||||
|
|
||||||
function handleDragOver(e: DragEvent) {
|
function handleDragOver(e: DragEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isDragOver = true;
|
isDragOver = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user