This commit is contained in:
oneao committed 2026-01-30 17:22:20 +08:00
1 parent c62c9d080b
commit 6a8c31e20c
29 files changed
+1454 -357

No files matched your search

@@ -0,0 +1,105 @@
// Code generated by hertz generator.
package money
import (
money "allapp/biz/model/money"
"allapp/biz/router/middleware"
"allapp/db"
"allapp/db/repo"
"allapp/utils/errorx"
"allapp/utils/pgtypex"
"allapp/utils/response"
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// AnalysisMoneyLine .
// @router /money/analysis/line [POST]
func AnalysisMoneyLine(ctx context.Context, c *app.RequestContext) {
var err error
var req money.AnalysisMoneyParams
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
userID := middleware.GetUserID(ctx)
analysis, err := db.Queries.ListMoneyRecordsByLineAnalysis(ctx, repo.ListMoneyRecordsByLineAnalysisParams{
Column1: req.PeriodType,
UserID: userID,
RecordTime: pgtypex.StringToDate(req.StartDate),
RecordTime_2: pgtypex.StringToDate(req.EndDate),
Type: req.Type,
})
if err != nil {
errorx.AddError(c, err)
return
}
// 循环赋值到响应数据中
records := make([]*money.AnalysisMoneyAmount, 0, len(analysis))
for _, record := range analysis {
period, ok := record.Period.(string)
if !ok {
period = ""
}
// 创建指向 AnalysisAmount 的指针
records = append(records, &money.AnalysisMoneyAmount{
X: period,
Y: pgtypex.NumericToString(record.TotalAmount),
})
}
response.Success(c).Data(&money.AnalysisMoneyLineResult{
Records: records,
}).Send()
}
// AnalysisMoneyPie .
// @router /money/analysis/pie [POST]
func AnalysisMoneyPie(ctx context.Context, c *app.RequestContext) {
var err error
var req money.AnalysisMoneyParams
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
userID := middleware.GetUserID(ctx)
analysis, err := db.Queries.ListMoneyRecordsByPieAnalysis(ctx, repo.ListMoneyRecordsByPieAnalysisParams{
UserID: userID,
RecordTime: pgtypex.StringToDate(req.StartDate),
RecordTime_2: pgtypex.StringToDate(req.EndDate),
Type: req.Type,
})
if err != nil {
errorx.AddError(c, err)
return
}
// 使用值类型而不是指针类型
records := make([]*money.AnalysisMoneyAmount, 0, len(analysis))
for _, record := range analysis {
// 直接赋值而不是使用指针
records = append(records, &money.AnalysisMoneyAmount{
X: record.Name.String,
Y: pgtypex.NumericToString(record.TotalAmount),
})
}
response.Success(c).Data(&money.AnalysisMoneyPieResult{
Records: records,
}).Send()
}
@@ -3,7 +3,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v6.33.1
// protoc v4.25.3
// source: api.proto
package api
+109 -84
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v6.33.1
// protoc v4.25.3
// source: money.proto
package money
@@ -29,74 +29,91 @@ var file_money_proto_rawDesc = []byte{
0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x1e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x5f, 0x72,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x32, 0xcb, 0x04, 0x0a, 0x14, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67,
0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x04, 0x4c, 0x69,
0x73, 0x74, 0x12, 0x1b, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d,
0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a,
0x1c, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e, 0x65,
0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x13, 0xca,
0xc1, 0x18, 0x0f, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f,
0x72, 0x79, 0x12, 0x74, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1d, 0x2e,
0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65,
0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d,
0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79,
0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1a, 0xd2, 0xc1,
0x18, 0x16, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72,
0x79, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64,
0x6f, 0x1a, 0x20, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x5f, 0x61,
0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x32, 0xcb, 0x04, 0x0a, 0x14, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74,
0x65, 0x67, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x04,
0x4c, 0x69, 0x73, 0x74, 0x12, 0x1b, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73,
0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65,
0x71, 0x1a, 0x1c, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f,
0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22,
0x13, 0xca, 0xc1, 0x18, 0x0f, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61, 0x74, 0x65,
0x67, 0x6f, 0x72, 0x79, 0x12, 0x74, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12,
0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f,
0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1e,
0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e,
0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1a,
0xd2, 0xc1, 0x18, 0x16, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61, 0x74, 0x65, 0x67,
0x6f, 0x72, 0x79, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x17, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x21, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x6d, 0x6f, 0x6e, 0x65,
0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74,
0x65, 0x67, 0x6f, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1e, 0xd2,
0xc1, 0x18, 0x1a, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f,
0x72, 0x79, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x70, 0x0a,
0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65,
0x67, 0x6f, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x43, 0x72, 0x65,
0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
0x53, 0x6f, 0x72, 0x74, 0x12, 0x21, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
0x53, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x22, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67,
0x6f, 0x72, 0x79, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1e, 0xd2, 0xc1, 0x18,
0x1a, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x70, 0x0a, 0x13, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
0x72, 0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65,
0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73,
0x70, 0x22, 0x1a, 0xd2, 0xc1, 0x18, 0x16, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x63, 0x61,
0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x70, 0x0a,
0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65,
0x67, 0x6f, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61,
0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52,
0x65, 0x73, 0x70, 0x22, 0x1a, 0xd2, 0xc1, 0x18, 0x16, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f,
0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x32,
0xc7, 0x03, 0x0a, 0x12, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f,
0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x2e, 0x6d, 0x6f, 0x6e,
0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65,
0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x12, 0xd2, 0xc1, 0x18, 0x0e, 0x2f, 0x6d,
0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x6d, 0x0a, 0x11,
0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x12, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74,
0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12,
0x70, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61,
0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
0x72, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72,
0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1a, 0xd2, 0xc1, 0x18, 0x16, 0x2f, 0x6d, 0x6f, 0x6e, 0x65,
0x79, 0x2f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x32, 0xc7, 0x03, 0x0a, 0x12, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x2e, 0x6d,
0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x1d, 0x2e, 0x6d, 0x6f,
0x6e, 0x65, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x12, 0xd2, 0xc1, 0x18, 0x0e,
0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x6d,
0x0a, 0x11, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x12, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x49, 0x6e, 0x73, 0x65,
0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72,
0x61, 0x6d, 0x73, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x49, 0x6e, 0x73, 0x65,
0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x22, 0x18, 0xd2, 0xc1, 0x18, 0x14, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f,
0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x2f, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x12, 0x6d, 0x0a,
0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x12, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72, 0x61,
0x6d, 0x73, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x22, 0x18, 0xd2, 0xc1, 0x18, 0x14, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x72,
0x65, 0x63, 0x6f, 0x72, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x6d, 0x0a, 0x11,
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72,
0x64, 0x12, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d,
0x73, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74,
0x73, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c,
0x74, 0x22, 0x18, 0xd2, 0xc1, 0x18, 0x14, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x72, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x2f, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x12, 0x6d, 0x0a, 0x11, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
0x12, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d,
0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d,
0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x22, 0x18, 0xd2, 0xc1, 0x18, 0x14, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x72, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x6d, 0x0a, 0x11, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12,
0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f,
0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a,
0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f,
0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,
0x18, 0xd2, 0xc1, 0x18, 0x14, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x72, 0x65, 0x63, 0x6f,
0x72, 0x64, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x18, 0x5a, 0x16, 0x61, 0x6c, 0x6c,
0x61, 0x70, 0x70, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f,
0x6e, 0x65, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x63, 0x6f, 0x72, 0x64, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x32, 0xe9, 0x01, 0x0a, 0x14,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x69, 0x0a, 0x11, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x4c, 0x69, 0x6e, 0x65, 0x12, 0x1a, 0x2e, 0x6d, 0x6f, 0x6e, 0x65,
0x79, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x50,
0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x1e, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x41, 0x6e,
0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x4c, 0x69, 0x6e, 0x65, 0x52,
0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x18, 0xd2, 0xc1, 0x18, 0x14, 0x2f, 0x6d, 0x6f, 0x6e, 0x65,
0x79, 0x2f, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x65, 0x12,
0x66, 0x0a, 0x10, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e, 0x65, 0x79,
0x50, 0x69, 0x65, 0x12, 0x1a, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x41, 0x6e, 0x61, 0x6c,
0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a,
0x1d, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73,
0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x50, 0x69, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x17,
0xd2, 0xc1, 0x18, 0x13, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x61, 0x6e, 0x61, 0x6c, 0x79,
0x73, 0x69, 0x73, 0x2f, 0x70, 0x69, 0x65, 0x42, 0x18, 0x5a, 0x16, 0x61, 0x6c, 0x6c, 0x61, 0x70,
0x70, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x6e, 0x65,
0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var file_money_proto_goTypes = []interface{}{
@@ -109,15 +126,18 @@ var file_money_proto_goTypes = []interface{}{
(*InsertMoneyRecordParams)(nil), // 6: money.InsertMoneyRecordParams
(*UpdateMoneyRecordParams)(nil), // 7: money.UpdateMoneyRecordParams
(*DeleteMoneyRecordParams)(nil), // 8: money.DeleteMoneyRecordParams
(*ListMoneyCategoryResp)(nil), // 9: money.ListMoneyCategoryResp
(*DeleteMoneyCategoryResp)(nil), // 10: money.DeleteMoneyCategoryResp
(*UpdateMoneyCategorySortResp)(nil), // 11: money.UpdateMoneyCategorySortResp
(*CreateMoneyCategoryResp)(nil), // 12: money.CreateMoneyCategoryResp
(*UpdateMoneyCategoryResp)(nil), // 13: money.UpdateMoneyCategoryResp
(*ListMoneyRecordsResult)(nil), // 14: money.ListMoneyRecordsResult
(*InsertMoneyRecordResult)(nil), // 15: money.InsertMoneyRecordResult
(*UpdateMoneyRecordResult)(nil), // 16: money.UpdateMoneyRecordResult
(*DeleteMoneyRecordResult)(nil), // 17: money.DeleteMoneyRecordResult
(*AnalysisMoneyParams)(nil), // 9: money.AnalysisMoneyParams
(*ListMoneyCategoryResp)(nil), // 10: money.ListMoneyCategoryResp
(*DeleteMoneyCategoryResp)(nil), // 11: money.DeleteMoneyCategoryResp
(*UpdateMoneyCategorySortResp)(nil), // 12: money.UpdateMoneyCategorySortResp
(*CreateMoneyCategoryResp)(nil), // 13: money.CreateMoneyCategoryResp
(*UpdateMoneyCategoryResp)(nil), // 14: money.UpdateMoneyCategoryResp
(*ListMoneyRecordsResult)(nil), // 15: money.ListMoneyRecordsResult
(*InsertMoneyRecordResult)(nil), // 16: money.InsertMoneyRecordResult
(*UpdateMoneyRecordResult)(nil), // 17: money.UpdateMoneyRecordResult
(*DeleteMoneyRecordResult)(nil), // 18: money.DeleteMoneyRecordResult
(*AnalysisMoneyLineResult)(nil), // 19: money.AnalysisMoneyLineResult
(*AnalysisMoneyPieResult)(nil), // 20: money.AnalysisMoneyPieResult
}
var file_money_proto_depIdxs = []int32{
0, // 0: money.MoneyCategoryService.List:input_type -> money.ListMoneyCategoryReq
@@ -129,17 +149,21 @@ var file_money_proto_depIdxs = []int32{
6, // 6: money.MoneyRecordService.InsertMoneyRecord:input_type -> money.InsertMoneyRecordParams
7, // 7: money.MoneyRecordService.UpdateMoneyRecord:input_type -> money.UpdateMoneyRecordParams
8, // 8: money.MoneyRecordService.DeleteMoneyRecord:input_type -> money.DeleteMoneyRecordParams
9, // 9: money.MoneyCategoryService.List:output_type -> money.ListMoneyCategoryResp
10, // 10: money.MoneyCategoryService.DeleteUserMoneyCategory:output_type -> money.DeleteMoneyCategoryResp
11, // 11: money.MoneyCategoryService.UpdateMoneyCategorySort:output_type -> money.UpdateMoneyCategorySortResp
12, // 12: money.MoneyCategoryService.CreateMoneyCategory:output_type -> money.CreateMoneyCategoryResp
13, // 13: money.MoneyCategoryService.UpdateMoneyCategory:output_type -> money.UpdateMoneyCategoryResp
14, // 14: money.MoneyRecordService.ListMoneyRecords:output_type -> money.ListMoneyRecordsResult
15, // 15: money.MoneyRecordService.InsertMoneyRecord:output_type -> money.InsertMoneyRecordResult
16, // 16: money.MoneyRecordService.UpdateMoneyRecord:output_type -> money.UpdateMoneyRecordResult
17, // 17: money.MoneyRecordService.DeleteMoneyRecord:output_type -> money.DeleteMoneyRecordResult
9, // [9:18] is the sub-list for method output_type
0, // [0:9] is the sub-list for method input_type
9, // 9: money.MoneyAnalysisService.AnalysisMoneyLine:input_type -> money.AnalysisMoneyParams
9, // 10: money.MoneyAnalysisService.AnalysisMoneyPie:input_type -> money.AnalysisMoneyParams
10, // 11: money.MoneyCategoryService.List:output_type -> money.ListMoneyCategoryResp
11, // 12: money.MoneyCategoryService.DeleteUserMoneyCategory:output_type -> money.DeleteMoneyCategoryResp
12, // 13: money.MoneyCategoryService.UpdateMoneyCategorySort:output_type -> money.UpdateMoneyCategorySortResp
13, // 14: money.MoneyCategoryService.CreateMoneyCategory:output_type -> money.CreateMoneyCategoryResp
14, // 15: money.MoneyCategoryService.UpdateMoneyCategory:output_type -> money.UpdateMoneyCategoryResp
15, // 16: money.MoneyRecordService.ListMoneyRecords:output_type -> money.ListMoneyRecordsResult
16, // 17: money.MoneyRecordService.InsertMoneyRecord:output_type -> money.InsertMoneyRecordResult
17, // 18: money.MoneyRecordService.UpdateMoneyRecord:output_type -> money.UpdateMoneyRecordResult
18, // 19: money.MoneyRecordService.DeleteMoneyRecord:output_type -> money.DeleteMoneyRecordResult
19, // 20: money.MoneyAnalysisService.AnalysisMoneyLine:output_type -> money.AnalysisMoneyLineResult
20, // 21: money.MoneyAnalysisService.AnalysisMoneyPie:output_type -> money.AnalysisMoneyPieResult
11, // [11:22] is the sub-list for method output_type
0, // [0:11] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -152,6 +176,7 @@ func file_money_proto_init() {
}
file_money_money_category_types_proto_init()
file_money_money_record_types_proto_init()
file_money_money_analysis_types_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
@@ -160,7 +185,7 @@ func file_money_proto_init() {
NumEnums: 0,
NumMessages: 0,
NumExtensions: 0,
NumServices: 2,
NumServices: 3,
},
GoTypes: file_money_proto_goTypes,
DependencyIndexes: file_money_proto_depIdxs,
@@ -0,0 +1,385 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v4.25.3
// source: money/money_analysis_types.proto
package money
import (
_ "allapp/biz/model/api"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type AnalysisMoneyParams struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StartDate string `protobuf:"bytes,1,opt,name=StartDate,proto3" form:"start_date" json:"start_date"` // 开始时间
EndDate string `protobuf:"bytes,2,opt,name=EndDate,proto3" form:"end_date" json:"end_date"` // 结束时间
Type int32 `protobuf:"varint,3,opt,name=Type,proto3" form:"type" json:"type"` // 0 支出 1 收入
PeriodType int32 `protobuf:"varint,4,opt,name=PeriodType,proto3" form:"period_type" json:"period_type"` // 0 = 日分析, 1 = 月分析, 2 = 年分析
}
func (x *AnalysisMoneyParams) Reset() {
*x = AnalysisMoneyParams{}
if protoimpl.UnsafeEnabled {
mi := &file_money_money_analysis_types_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AnalysisMoneyParams) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AnalysisMoneyParams) ProtoMessage() {}
func (x *AnalysisMoneyParams) ProtoReflect() protoreflect.Message {
mi := &file_money_money_analysis_types_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AnalysisMoneyParams.ProtoReflect.Descriptor instead.
func (*AnalysisMoneyParams) Descriptor() ([]byte, []int) {
return file_money_money_analysis_types_proto_rawDescGZIP(), []int{0}
}
func (x *AnalysisMoneyParams) GetStartDate() string {
if x != nil {
return x.StartDate
}
return ""
}
func (x *AnalysisMoneyParams) GetEndDate() string {
if x != nil {
return x.EndDate
}
return ""
}
func (x *AnalysisMoneyParams) GetType() int32 {
if x != nil {
return x.Type
}
return 0
}
func (x *AnalysisMoneyParams) GetPeriodType() int32 {
if x != nil {
return x.PeriodType
}
return 0
}
// PeriodAmount 用于存储按天、月、年汇总的金额
type AnalysisMoneyAmount struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
X string `protobuf:"bytes,1,opt,name=X,proto3" form:"x" json:"x"` // 时间段(按天、月、年)
Y string `protobuf:"bytes,2,opt,name=Y,proto3" form:"y" json:"y"` // 汇总金额
}
func (x *AnalysisMoneyAmount) Reset() {
*x = AnalysisMoneyAmount{}
if protoimpl.UnsafeEnabled {
mi := &file_money_money_analysis_types_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AnalysisMoneyAmount) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AnalysisMoneyAmount) ProtoMessage() {}
func (x *AnalysisMoneyAmount) ProtoReflect() protoreflect.Message {
mi := &file_money_money_analysis_types_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AnalysisMoneyAmount.ProtoReflect.Descriptor instead.
func (*AnalysisMoneyAmount) Descriptor() ([]byte, []int) {
return file_money_money_analysis_types_proto_rawDescGZIP(), []int{1}
}
func (x *AnalysisMoneyAmount) GetX() string {
if x != nil {
return x.X
}
return ""
}
func (x *AnalysisMoneyAmount) GetY() string {
if x != nil {
return x.Y
}
return ""
}
type AnalysisMoneyLineResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Records []*AnalysisMoneyAmount `protobuf:"bytes,1,rep,name=Records,proto3" form:"records" json:"records"`
}
func (x *AnalysisMoneyLineResult) Reset() {
*x = AnalysisMoneyLineResult{}
if protoimpl.UnsafeEnabled {
mi := &file_money_money_analysis_types_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AnalysisMoneyLineResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AnalysisMoneyLineResult) ProtoMessage() {}
func (x *AnalysisMoneyLineResult) ProtoReflect() protoreflect.Message {
mi := &file_money_money_analysis_types_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AnalysisMoneyLineResult.ProtoReflect.Descriptor instead.
func (*AnalysisMoneyLineResult) Descriptor() ([]byte, []int) {
return file_money_money_analysis_types_proto_rawDescGZIP(), []int{2}
}
func (x *AnalysisMoneyLineResult) GetRecords() []*AnalysisMoneyAmount {
if x != nil {
return x.Records
}
return nil
}
type AnalysisMoneyPieResult struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Records []*AnalysisMoneyAmount `protobuf:"bytes,1,rep,name=Records,proto3" form:"records" json:"records"`
}
func (x *AnalysisMoneyPieResult) Reset() {
*x = AnalysisMoneyPieResult{}
if protoimpl.UnsafeEnabled {
mi := &file_money_money_analysis_types_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AnalysisMoneyPieResult) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AnalysisMoneyPieResult) ProtoMessage() {}
func (x *AnalysisMoneyPieResult) ProtoReflect() protoreflect.Message {
mi := &file_money_money_analysis_types_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AnalysisMoneyPieResult.ProtoReflect.Descriptor instead.
func (*AnalysisMoneyPieResult) Descriptor() ([]byte, []int) {
return file_money_money_analysis_types_proto_rawDescGZIP(), []int{3}
}
func (x *AnalysisMoneyPieResult) GetRecords() []*AnalysisMoneyAmount {
if x != nil {
return x.Records
}
return nil
}
var File_money_money_analysis_types_proto protoreflect.FileDescriptor
var file_money_money_analysis_types_proto_rawDesc = []byte{
0x0a, 0x20, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x5f, 0x61, 0x6e,
0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x05, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x1a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x01, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69,
0x73, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x2c, 0x0a, 0x09,
0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42,
0x0e, 0xca, 0xbb, 0x18, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x52,
0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x07, 0x45, 0x6e,
0x64, 0x44, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xca, 0xbb, 0x18,
0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x52, 0x07, 0x45, 0x6e, 0x64, 0x44, 0x61,
0x74, 0x65, 0x12, 0x1c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
0x42, 0x08, 0xca, 0xbb, 0x18, 0x04, 0x74, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65,
0x12, 0x2f, 0x0a, 0x0a, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x54, 0x79, 0x70, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x05, 0x42, 0x0f, 0xca, 0xbb, 0x18, 0x0b, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64,
0x5f, 0x74, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x54, 0x79, 0x70,
0x65, 0x22, 0x3f, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e,
0x65, 0x79, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x01, 0x58, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x42, 0x05, 0xca, 0xbb, 0x18, 0x01, 0x78, 0x52, 0x01, 0x58, 0x12, 0x13, 0x0a,
0x01, 0x59, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x05, 0xca, 0xbb, 0x18, 0x01, 0x79, 0x52,
0x01, 0x59, 0x22, 0x5c, 0x0a, 0x17, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f,
0x6e, 0x65, 0x79, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x41, 0x0a,
0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d,
0x6f, 0x6e, 0x65, 0x79, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0b, 0xca, 0xbb, 0x18, 0x07,
0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73,
0x22, 0x5b, 0x0a, 0x16, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e, 0x65,
0x79, 0x50, 0x69, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x41, 0x0a, 0x07, 0x52, 0x65,
0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x6f,
0x6e, 0x65, 0x79, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x4d, 0x6f, 0x6e, 0x65,
0x79, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0b, 0xca, 0xbb, 0x18, 0x07, 0x72, 0x65, 0x63,
0x6f, 0x72, 0x64, 0x73, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x42, 0x18, 0x5a,
0x16, 0x61, 0x6c, 0x6c, 0x61, 0x70, 0x70, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65,
0x6c, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_money_money_analysis_types_proto_rawDescOnce sync.Once
file_money_money_analysis_types_proto_rawDescData = file_money_money_analysis_types_proto_rawDesc
)
func file_money_money_analysis_types_proto_rawDescGZIP() []byte {
file_money_money_analysis_types_proto_rawDescOnce.Do(func() {
file_money_money_analysis_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_money_money_analysis_types_proto_rawDescData)
})
return file_money_money_analysis_types_proto_rawDescData
}
var file_money_money_analysis_types_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_money_money_analysis_types_proto_goTypes = []interface{}{
(*AnalysisMoneyParams)(nil), // 0: money.AnalysisMoneyParams
(*AnalysisMoneyAmount)(nil), // 1: money.AnalysisMoneyAmount
(*AnalysisMoneyLineResult)(nil), // 2: money.AnalysisMoneyLineResult
(*AnalysisMoneyPieResult)(nil), // 3: money.AnalysisMoneyPieResult
}
var file_money_money_analysis_types_proto_depIdxs = []int32{
1, // 0: money.AnalysisMoneyLineResult.Records:type_name -> money.AnalysisMoneyAmount
1, // 1: money.AnalysisMoneyPieResult.Records:type_name -> money.AnalysisMoneyAmount
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_money_money_analysis_types_proto_init() }
func file_money_money_analysis_types_proto_init() {
if File_money_money_analysis_types_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_money_money_analysis_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AnalysisMoneyParams); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_money_money_analysis_types_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AnalysisMoneyAmount); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_money_money_analysis_types_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AnalysisMoneyLineResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_money_money_analysis_types_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AnalysisMoneyPieResult); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_money_money_analysis_types_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_money_money_analysis_types_proto_goTypes,
DependencyIndexes: file_money_money_analysis_types_proto_depIdxs,
MessageInfos: file_money_money_analysis_types_proto_msgTypes,
}.Build()
File_money_money_analysis_types_proto = out.File
file_money_money_analysis_types_proto_rawDesc = nil
file_money_money_analysis_types_proto_goTypes = nil
file_money_money_analysis_types_proto_depIdxs = nil
}
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v6.33.1
// protoc v4.25.3
// source: money/money_category_types.proto
package money
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v6.33.1
// protoc v4.25.3
// source: money/money_record_types.proto
package money
@@ -25,6 +25,7 @@ func Auth() app.HandlerFunc {
"/auth/login",
"/auth/register",
"/auth/refresh/token",
"/ws",
}
return func(ctx context.Context, c *app.RequestContext) {
@@ -80,3 +80,23 @@ func _listmoneyrecordsMw() []app.HandlerFunc {
// your code...
return nil
}
func _analysismoneyMw() []app.HandlerFunc {
// your code...
return nil
}
func _analysisMw() []app.HandlerFunc {
// your code...
return nil
}
func _analysismoneylineMw() []app.HandlerFunc {
// your code...
return nil
}
func _analysismoneypieMw() []app.HandlerFunc {
// your code...
return nil
}
@@ -26,6 +26,11 @@ func Register(r *server.Hertz) {
_category.POST("/update", append(_updatemoneycategoryMw(), money.UpdateMoneyCategory)...)
_category.POST("/updateSort", append(_updatemoneycategorysortMw(), money.UpdateMoneyCategorySort)...)
_money.POST("/records", append(_listmoneyrecordsMw(), money.ListMoneyRecords)...)
{
_analysis := _money.Group("/analysis", _analysisMw()...)
_analysis.POST("/line", append(_analysismoneylineMw(), money.AnalysisMoneyLine)...)
_analysis.POST("/pie", append(_analysismoneypieMw(), money.AnalysisMoneyPie)...)
}
{
_record := _money.Group("/record", _recordMw()...)
_record.POST("/delete", append(_deletemoneyrecordMw(), money.DeleteMoneyRecord)...)
+1
View File
@@ -29,6 +29,7 @@ type Config struct {
type ServerConfig struct {
Port string `mapstructure:"port" yaml:"port"`
BaseUrl string `mapstructure:"base_url" yaml:"base_url"` // 下划线
WsUrl string `mapstructure:"ws_url" yaml:"ws_url"`
}
type JwtConfig struct {
+1 -1
View File
@@ -1,7 +1,7 @@
server:
port: 8086
base_url: /app
ws_url: /ws
jwt:
secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
access_expiry: -1
@@ -52,4 +52,59 @@ RETURNING *;
DELETE FROM money_record WHERE id = $1;
-- name: DeleteMoneyRecordByCategoryId :exec
DELETE FROM money_record WHERE user_id = $1 AND category_id = $2;
DELETE FROM money_record WHERE user_id = $1 AND category_id = $2;
-- 折线图:按天、月、年汇总金额
-- name: ListMoneyRecordsByLineAnalysis :many
SELECT
CASE
WHEN $1 = 0 THEN to_char(mr.record_time, 'DD') -- 日分析,转换为文本
WHEN $1 = 1 THEN to_char(mr.record_time, 'MM') -- 月分析,转换为文本
WHEN $1 = 2 THEN to_char(mr.record_time, 'YYYY') -- 年分析,转换为文本
END AS period,
SUM(mr.amount)::DECIMAL AS total_amount
FROM
money_record mr
LEFT JOIN
money_user_category muc ON mr.category_id = muc.id -- 连接 money_user_category 获取 type
LEFT JOIN
space_user su ON su.user_id = $2 -- 连接 space_user 表,查找该用户在空间中的信息
LEFT JOIN
space s ON s.id = su.space_id -- 连接 space 表,获取空间信息
WHERE
(
(su.user_id IS NOT NULL) -- 用户在空间,查询该空间内所有人的数据
OR (mr.user_id = $1) -- 用户不在空间,查询该用户个人的账本数据
)
AND mr.record_time BETWEEN $3 AND $4 -- 时间范围
AND muc.type = $5 -- 收入或支出类型
GROUP BY
period
ORDER BY
period;
-- 饼图:按类别汇总金额
-- name: ListMoneyRecordsByPieAnalysis :many
SELECT
muc.name,
SUM(mr.amount)::DECIMAL AS total_amount
FROM
money_record mr
LEFT JOIN
money_user_category muc ON mr.category_id = muc.id
LEFT JOIN
space_user su ON su.user_id = $1 -- 连接 space_user 表,查找该用户在空间中的信息
LEFT JOIN
space s ON s.id = su.space_id -- 连接 space 表,获取空间信息
WHERE
(
(su.user_id IS NOT NULL) -- 用户在空间,查询该空间内所有人的数据
OR (mr.user_id = $1) -- 用户不在空间,查询该用户个人的账本数据
)
AND mr.record_time BETWEEN $2 AND $3 -- 时间范围
AND muc.type = $4 -- 收入或支出类型
GROUP BY
muc.name
ORDER BY
total_amount DESC;
@@ -160,6 +160,138 @@ func (q *Queries) ListMoneyRecords(ctx context.Context, arg ListMoneyRecordsPara
return items, nil
}
const listMoneyRecordsByLineAnalysis = `-- name: ListMoneyRecordsByLineAnalysis :many
SELECT
CASE
WHEN $1 = 0 THEN to_char(mr.record_time, 'DD') -- 日分析,转换为文本
WHEN $1 = 1 THEN to_char(mr.record_time, 'MM') -- 月分析,转换为文本
WHEN $1 = 2 THEN to_char(mr.record_time, 'YYYY') -- 年分析,转换为文本
END AS period,
SUM(mr.amount)::DECIMAL AS total_amount
FROM
money_record mr
LEFT JOIN
money_user_category muc ON mr.category_id = muc.id -- 连接 money_user_category 获取 type
LEFT JOIN
space_user su ON su.user_id = $2 -- 连接 space_user 表,查找该用户在空间中的信息
LEFT JOIN
space s ON s.id = su.space_id -- 连接 space 表,获取空间信息
WHERE
(
(su.user_id IS NOT NULL) -- 用户在空间,查询该空间内所有人的数据
OR (mr.user_id = $1) -- 用户不在空间,查询该用户个人的账本数据
)
AND mr.record_time BETWEEN $3 AND $4 -- 时间范围
AND muc.type = $5 -- 收入或支出类型
GROUP BY
period
ORDER BY
period
`
type ListMoneyRecordsByLineAnalysisParams struct {
Column1 interface{}
UserID int64
RecordTime pgtype.Date
RecordTime_2 pgtype.Date
Type int32
}
type ListMoneyRecordsByLineAnalysisRow struct {
Period interface{}
TotalAmount pgtype.Numeric
}
// 折线图:按天、月、年汇总金额
func (q *Queries) ListMoneyRecordsByLineAnalysis(ctx context.Context, arg ListMoneyRecordsByLineAnalysisParams) ([]ListMoneyRecordsByLineAnalysisRow, error) {
rows, err := q.db.Query(ctx, listMoneyRecordsByLineAnalysis,
arg.Column1,
arg.UserID,
arg.RecordTime,
arg.RecordTime_2,
arg.Type,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListMoneyRecordsByLineAnalysisRow
for rows.Next() {
var i ListMoneyRecordsByLineAnalysisRow
if err := rows.Scan(&i.Period, &i.TotalAmount); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listMoneyRecordsByPieAnalysis = `-- name: ListMoneyRecordsByPieAnalysis :many
SELECT
muc.name,
SUM(mr.amount)::DECIMAL AS total_amount
FROM
money_record mr
LEFT JOIN
money_user_category muc ON mr.category_id = muc.id
LEFT JOIN
space_user su ON su.user_id = $1 -- 连接 space_user 表,查找该用户在空间中的信息
LEFT JOIN
space s ON s.id = su.space_id -- 连接 space 表,获取空间信息
WHERE
(
(su.user_id IS NOT NULL) -- 用户在空间,查询该空间内所有人的数据
OR (mr.user_id = $1) -- 用户不在空间,查询该用户个人的账本数据
)
AND mr.record_time BETWEEN $2 AND $3 -- 时间范围
AND muc.type = $4 -- 收入或支出类型
GROUP BY
muc.name
ORDER BY
total_amount DESC
`
type ListMoneyRecordsByPieAnalysisParams struct {
UserID int64
RecordTime pgtype.Date
RecordTime_2 pgtype.Date
Type int32
}
type ListMoneyRecordsByPieAnalysisRow struct {
Name pgtype.Text
TotalAmount pgtype.Numeric
}
// 饼图:按类别汇总金额
func (q *Queries) ListMoneyRecordsByPieAnalysis(ctx context.Context, arg ListMoneyRecordsByPieAnalysisParams) ([]ListMoneyRecordsByPieAnalysisRow, error) {
rows, err := q.db.Query(ctx, listMoneyRecordsByPieAnalysis,
arg.UserID,
arg.RecordTime,
arg.RecordTime_2,
arg.Type,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListMoneyRecordsByPieAnalysisRow
for rows.Next() {
var i ListMoneyRecordsByPieAnalysisRow
if err := rows.Scan(&i.Name, &i.TotalAmount); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateMoneyRecord = `-- name: UpdateMoneyRecord :exec
UPDATE money_record
SET
+1
View File
@@ -28,6 +28,7 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
+2
View File
@@ -49,6 +49,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/henrylee2cn/ameda v1.4.8/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
github.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=
github.com/henrylee2cn/goutil v0.0.0-20210127050712-89660552f6f8/go.mod h1:Nhe/DM3671a5udlv2AdV2ni/MZzgfv2qrPL5nIi3EGQ=
@@ -7,6 +7,7 @@ option go_package = "money";
import "api.proto";
import "money/money_category_types.proto";
import "money/money_record_types.proto";
import "money/money_analysis_types.proto";
service MoneyCategoryService {
rpc List(ListMoneyCategoryReq) returns (ListMoneyCategoryResp){
@@ -40,3 +41,12 @@ service MoneyRecordService {
option (api.post) = '/money/record/delete';
}
}
service MoneyAnalysisService {
rpc AnalysisMoneyLine(AnalysisMoneyParams) returns (AnalysisMoneyLineResult){
option (api.post) = '/money/analysis/line';
}
rpc AnalysisMoneyPie(AnalysisMoneyParams) returns (AnalysisMoneyPieResult){
option (api.post) = '/money/analysis/pie';
}
}
@@ -0,0 +1,27 @@
syntax = "proto3";
import "api.proto";
package money;
option go_package = "money";
message AnalysisMoneyParams {
string StartDate = 1 [(api.body) = "start_date"]; // 开始时间
string EndDate = 2 [(api.body) = "end_date"]; // 结束时间
int32 Type = 3 [(api.body) = "type"];// 0 支出 1 收入
int32 PeriodType = 4 [(api.body) = "period_type"]; // 0 = 日分析, 1 = 月分析, 2 = 年分析
}
// PeriodAmount 用于存储按天、月、年汇总的金额
message AnalysisMoneyAmount {
string X = 1 [(api.body) = "x"]; // 时间段(按天、月、年)
string Y = 2 [(api.body) = "y"]; // 汇总金额
}
message AnalysisMoneyLineResult {
repeated AnalysisMoneyAmount Records = 1 [(api.body) = "records"];
}
message AnalysisMoneyPieResult {
repeated AnalysisMoneyAmount Records = 1 [(api.body) = "records"];
}
+37
View File
@@ -8,11 +8,46 @@ import (
"allapp/utils/idgen"
"allapp/utils/jwtx"
"allapp/utils/redisx"
"log"
"net/http"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/adaptor"
"github.com/gorilla/websocket"
"github.com/hertz-contrib/requestid"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源的连接
},
}
func echo(w http.ResponseWriter, r *http.Request) {
// 这里是通过升级协议来实现 WebSocket 连接
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
// 不断监听 WebSocket 连接的数据
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = c.WriteMessage(mt, message) // 回显消息
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
// 初始化雪花id
options := idgen.NewIdGeneratorOptions(conf.GetConf().Snowflake.WorkerID)
@@ -39,6 +74,8 @@ func main() {
h.Use(middleware.Auth())
h.Use(middleware.RefreshActive())
h.GET(conf.GetConf().Server.WsUrl, adaptor.HertzHandler(http.HandlerFunc(echo)))
register(h)
h.Spin()
}
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from 'react'
import { Text, TouchableOpacity, View } from 'react-native'
import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables'
import { ADrawer, LucideIcon } from '@/components/common/ao'
import { ADivider, ADrawer, LucideIcon } from '@/components/common/ao'
import { useNavStore } from '@/store/navigation'
export default function TabsLayout() {
@@ -75,37 +75,42 @@ export default function TabsLayout() {
<View
className="
absolute
left-3 right-3 bottom-8
h-14
flex-row items-center
px-2
gap-4
bg-white
"
style={{ zIndex: 0 }}
style={{
zIndex: 0,
elevation: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 100 },
}}
>
<View className="bg-white rounded-md shadow-lg h-12 w-12">
<TouchableOpacity className="w-full h-full items-center justify-center">
<LucideIcon icon="Bot" size={26} clickable={false} strokeWidth={2} />
</TouchableOpacity>
</View>
<TouchableOpacity className="w-12 items-center justify-center">
<LucideIcon icon="Bot" size={24} clickable={false} strokeWidth={2} />
</TouchableOpacity>
<View className="flex-1 bg-white rounded-md shadow-lg h-12">
<TouchableOpacity
className="w-full h-full items-center justify-center flex flex-row"
onPress={() => {
setShowNavDrawer(true)
}}
>
<LucideIcon icon={currentNav.icon} size={20} strokeWidth={2} />
<Text className="font-semibold text-lg ml-2">{currentNav.label}</Text>
</TouchableOpacity>
</View>
<View className="bg-white rounded-md shadow-lg h-12 w-12">
<TouchableOpacity className="w-full h-full items-center justify-center" onPress={refreshPage}>
<LucideIcon icon="RefreshCcw" size={26} clickable={false} strokeWidth={2} />
</TouchableOpacity>
</View>
<ADivider
direction="vertical"
/>
<TouchableOpacity
className="flex-1 items-center justify-center flex flex-row"
onPress={() => {
setShowNavDrawer(true)
}}
>
<LucideIcon icon={currentNav.icon} size={22} strokeWidth={2} />
<Text className="font-semibold text-lg ml-2">{currentNav.label}</Text>
</TouchableOpacity>
<ADivider direction="vertical" />
<TouchableOpacity className="w-12 items-center justify-center" onPress={refreshPage}>
<LucideIcon icon="RefreshCcw" size={24} clickable={false} strokeWidth={2} />
</TouchableOpacity>
</View>
<ADrawer
@@ -1,16 +1,11 @@
import type { MoneyRecord, MoneyRecordGroup } from '@/types/money/record'
import React, { useCallback, useState } from 'react'
import { RefreshControl, SectionList, Text, TouchableHighlight, Vibration, View } from 'react-native'
import {
Menu,
MenuOption,
MenuOptions,
MenuTrigger,
renderers,
} from 'react-native-popup-menu'
import { Pressable, RefreshControl, SectionList, Text, Vibration, View } from 'react-native'
import { LucideIcon } from '@/components/common/ao'
import EmptyState from '@/components/common/EmptyStatus'
import MoneyCategoryIcon from '@/components/common/MoneyCategoryIcon'
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
import { useMoneyCategoryStore } from '@/store/moneyCategory'
import { useUserStore } from '@/store/user'
import { MoneyCategoryEnum } from '@/types/money/category'
@@ -124,124 +119,121 @@ export default function StaticTransactions({
const category = moneyCategoryStore.getCategoryById(item.category_id)
return (
<Menu
renderer={renderers.Popover}
rendererProps={{
placement: 'bottom',
preferredPlacement: 'bottom',
anchorStyle: { backgroundColor: 'transparent' },
closeAnimationDuration: 0,
style={{
elevation: 10,
borderWidth: 0,
shadowColor: '#000',
borderRadius: 4,
width: 160,
}}
onSelect={() => setSelectedId(null)}
onBackdropPress={() => setSelectedId(null)}
onOpen={() => setSelectedId(item.id)}
onClose={() => setSelectedId(null)}
>
<MenuTrigger
triggerOnLongPress
onPress={() => {
Vibration.vibrate(50)
}}
>
<View
className="flex-row items-center gap-4 py-2 px-5"
style={{
backgroundColor: item.id === selectedId ? '#f2f2f2' : 'transparent',
}}
>
<View className="relative w-10 h-10 rounded-full bg-gray-100 items-center justify-center">
<MoneyCategoryIcon name={category?.icon} size={24} color="#000" />
</View>
<View className="flex-1">
<Text className="text-gray-800 text-base">
{item.remark ? item.remark : category?.name}
</Text>
{
item.user_id !== userID && (
<Text className="text-gray-500 text-xs italic">
{item.username}
</Text>
)
}
</View>
<View>
<Text
className="text-sm text-gray-600 text-right"
offset={0}
placement="bottom"
onClose={() => {
setSelectedId(null)
}}
trigger={({ ...triggerProps }) => {
return (
<Pressable
{...triggerProps}
delayLongPress={300}
onLongPress={(e) => {
setSelectedId(item.id)
Vibration.vibrate(50)
// 手动触发菜单显示
triggerProps.onPress?.(e)
}}
onPress={(_e) => {
// 阻止点击触发,可以执行其他点击操作
// 如果需要点击执行其他操作,可以在这里添加
}}
>
<View
className="flex-row items-center gap-4 py-2 px-5"
style={{
backgroundColor: item.id === selectedId ? '#f2f2f2' : 'transparent',
}}
>
{category?.type === MoneyCategoryEnum.Income ? '+' : '-'}
{' '}
{item.amount}
</Text>
</View>
</View>
</MenuTrigger>
<MenuOptions
customStyles={{
optionsContainer: {
backgroundColor: 'white',
minWidth: 160,
},
<View className="relative w-10 h-10 rounded-full bg-gray-100 items-center justify-center">
<MoneyCategoryIcon name={category?.icon} size={24} color="#000" />
</View>
<View className="flex-1">
<Text className="text-gray-800 text-base">
{item.remark ? item.remark : category?.name}
</Text>
{
item.user_id !== userID && (
<Text className="text-gray-500 text-xs italic">
{item.username}
</Text>
)
}
</View>
<View>
<Text
className="text-sm text-gray-600 text-right"
>
{category?.type === MoneyCategoryEnum.Income ? '+' : '-'}
{' '}
{item.amount}
</Text>
</View>
</View>
</Pressable>
)
}}
>
{
userID === item.user_id && (
<MenuItem
key="update"
textValue="update"
className="flex gap-2"
style={{
paddingVertical: 10,
minWidth: 150,
}}
onPress={() => {
onUpdate?.(item)
}}
>
<LucideIcon icon="Pencil" size={18} />
<MenuItemLabel>更新</MenuItemLabel>
</MenuItem>
)
}
<MenuItem
key="delete"
textValue="delete"
className="flex gap-2"
style={{
paddingVertical: 10,
minWidth: 150,
}}
onPress={() => {
onDelete?.(item.id)
}}
>
{
userID === item.user_id && (
<>
<MenuOption
customStyles={{
OptionTouchableComponent: TouchableHighlight,
optionTouchable: {
underlayColor: '#f0f0f0',
activeOpacity: 0.7,
},
}}
onSelect={() => {
onUpdate?.(item)
}}
>
<View className="flex flex-row items-center h-10 px-2">
<LucideIcon icon="Pencil" />
<Text className="ml-2 text-lg">更新</Text>
</View>
</MenuOption>
<MenuOption
customStyles={{
OptionTouchableComponent: TouchableHighlight,
optionTouchable: {
underlayColor: '#f0f0f0',
activeOpacity: 0.7,
},
}}
onSelect={() => {
onDelete?.(item.id)
}}
>
<View className="flex flex-row items-center h-10 px-2">
<LucideIcon icon="Trash" />
<Text className="ml-2 text-lg">删除</Text>
</View>
</MenuOption>
</>
)
}
<MenuOption
customStyles={{
OptionTouchableComponent: TouchableHighlight,
optionTouchable: {
underlayColor: '#f0f0f0',
activeOpacity: 0.7,
},
}}
onSelect={() => {
onDetail?.(item.id)
}}
>
<View className="flex flex-row items-center h-10 px-2">
<LucideIcon icon="ReceiptText" />
<Text className="ml-2 text-lg">详细</Text>
</View>
</MenuOption>
</MenuOptions>
<LucideIcon icon="Trash" size={18} />
<MenuItemLabel>删除</MenuItemLabel>
</MenuItem>
<MenuItem
key="detail"
textValue="detail"
className="flex gap-2"
style={{
paddingVertical: 10,
minWidth: 150,
}}
onPress={() => {
onDetail?.(item.id)
}}
>
<LucideIcon icon="ReceiptText" size={18} />
<MenuItemLabel>详细</MenuItemLabel>
</MenuItem>
</Menu>
)
}}
@@ -161,7 +161,6 @@ export default function Money() {
onPress={() => {
router.navigate('/money/record/editor')
}}
className="mb-20"
>
<FabIcon as={AddIcon} size="xl" />
</Fab>
@@ -1,38 +1,131 @@
import type { AnalysisMoneyAmount } from '@/types/money/analysis'
import dayjs from 'dayjs'
import { useState } from 'react'
import { Text, TouchableOpacity, View } from 'react-native'
import { VictoryChart, VictoryLine, VictoryPie, VictoryTheme, VictoryTooltip, VictoryVoronoiContainer } from 'victory-native'
import { ADatePicker, ASegmentedTabs, LucideIcon } from '@/components/common/ao'
import { useEffect, useState } from 'react'
import { ScrollView, Text, TouchableOpacity, View } from 'react-native'
import Svg from 'react-native-svg'
import { VictoryChart, VictoryLabel, VictoryLine, VictoryPie, VictoryTheme, VictoryTooltip } from 'victory-native'
import { ADatePicker, ADateRangePicker, ASegmentedTabs, LucideIcon } from '@/components/common/ao'
import EmptyState from '@/components/common/EmptyStatus'
import CustomHeader from '@/components/custom/Header'
import PagerTabs from '@/components/custom/PagerTabs'
type GroupBy = 'day' | 'month' | 'year'
type AnalysisType = 'expense' | 'income'
import { analysisMoneyLineApi, analysisMoneyPieApi } from '@/request/api/money/analysis'
import { MoneyCategoryEnum } from '@/types/money/category'
import { getCurrentYear, getCurrentYearMonth, getEndOfMonth, getStartOfMonth } from '@/utils/time'
interface AnalysisContentProps {
analysisType: AnalysisType
analysisType: MoneyCategoryEnum
}
export default function Analysis() {
function AnalysisContent({ analysisType }: AnalysisContentProps) {
// 按月0 按年1 自定义2
const [timeType, setTimeType] = useState<number>(0)
const [groupBy, setGroupBy] = useState<GroupBy>('day')
const [startDate, setStartDate] = useState(
dayjs().format('YYYY-MM'),
)
const [endDate, setEndDate] = useState(
dayjs().format('YYYY-MM'),
)
// 开始结束时间
const [startDate, setStartDate] = useState(getCurrentYearMonth())
const [endDate, setEndDate] = useState('')
// 显示
const [showDatePicker, setShowDatePicker] = useState(false)
// 数据
const [lineRecords, setLineRecords] = useState<AnalysisMoneyAmount[]>([])
const [pieRecords, setPieRecords] = useState<AnalysisMoneyAmount[]>([])
// 请求
function fetchChartsData() {
let start = ''
let end = ''
const selectedStartDate = dayjs(startDate)
const selectedEndDate = dayjs(endDate)
let periodType = 0
if (timeType === 0) {
start = selectedStartDate.startOf('month').format('YYYY-MM-DD')
end = selectedStartDate.endOf('month').format('YYYY-MM-DD')
periodType = 0
}
else if (timeType === 1) {
start = selectedStartDate.startOf('year').format('YYYY-MM-DD')
end = selectedStartDate.endOf('year').format('YYYY-MM-DD')
periodType = 1
}
else {
console.log(selectedStartDate.isSame(selectedEndDate, 'day'))
console.log(selectedStartDate.isSame(selectedEndDate, 'month'))
console.log(selectedStartDate.isSame(selectedEndDate, 'year'))
if (selectedStartDate.isSame(selectedEndDate, 'month')) {
periodType = 0
start = selectedStartDate.startOf('month').format('YYYY-MM-DD')
end = selectedEndDate.endOf('month').format('YYYY-MM-DD')
}
else if (selectedStartDate.isSame(selectedEndDate, 'year')) {
periodType = 1
start = selectedStartDate.startOf('year').format('YYYY-MM-DD')
end = selectedEndDate.endOf('year').format('YYYY-MM-DD')
}
else {
periodType = 2
start = selectedStartDate.format('YYYY-MM-DD')
end = selectedEndDate.format('YYYY-MM-DD')
}
}
analysisMoneyLineApi({
start_date: start,
end_date: end,
type: analysisType,
period_type: periodType,
}).then((res) => {
if (res.isSuccess) {
setLineRecords(res.data.records)
}
})
analysisMoneyPieApi({
start_date: start,
end_date: end,
type: analysisType,
period_type: periodType,
}).then((res) => {
if (res.isSuccess) {
setPieRecords(res.data.records)
}
})
}
useEffect(() => {
fetchChartsData()
}, [timeType, startDate, endDate])
return (
<View className="p-4">
<ScrollView
className="p-4"
>
<ASegmentedTabs
value={timeType}
onChange={(index) => {
setTimeType(index)
if (index === 2) {
// 自定义类型,本月的 1号 - 终止号
setStartDate(getStartOfMonth())
setEndDate(getEndOfMonth())
}
else if (index === 1) {
// 按年
setStartDate(getCurrentYear())
setEndDate('')
}
else {
// 按月
setStartDate(getCurrentYearMonth())
setEndDate('')
}
}}
tabs={[
{ key: '1', label: '按月' },
@@ -42,9 +135,9 @@ export default function Analysis() {
/>
<TouchableOpacity
className="mt-14 flex flex-row justify-center items-center"
className="flex flex-row justify-center items-center"
>
<LucideIcon icon="Calendar" size={16} color="#666" clickable={false} />
<LucideIcon icon="CalendarDays" size={14} color="#666" clickable={false} />
<Text className="font-semibold text-lg mr-2 ml-1">{startDate}</Text>
{timeType === 2 && (
<>
@@ -63,40 +156,99 @@ export default function Analysis() {
<VictoryChart
height={220}
theme={VictoryTheme.material}
padding={{ top: 15, bottom: 30, left: 40, right: 40 }}
>
<VictoryLine
interpolation="catmullRom"
data={[
]}
data={lineRecords}
animate={{
duration: 1000,
}}
/>
</VictoryChart>
</View>
<View className="mt-4">
<View
className="mt-4"
>
<Text className="text-lg font-semibold text-gray-700">分类构成</Text>
<VictoryPie
height={250}
innerRadius={50}
data={[
]}
theme={VictoryTheme.material}
/>
{
pieRecords.length > 0
? (
<VictoryPie
height={250}
animate={{ duration: 1000 }}
data={pieRecords}
theme={VictoryTheme.material}
/>
)
: (
<EmptyState title="空" />
)
}
</View>
<ADatePicker
show={showDatePicker}
mode="month"
onConfirm={(val) => {
setShowDatePicker(false)
}}
onClose={() => {
setShowDatePicker(false)
}}
/>
</View>
<View className="mt-4" style={{ paddingBottom: 24 }}>
<Text className="text-lg font-semibold text-gray-700">分类详细</Text>
{
pieRecords.length > 0
? (
<View className="mt-4 space-y-3">
{pieRecords.map((item, index) => (
<View
key={index}
className={`flex flex-row justify-between items-center p-3 bg-white shadow-md rounded-lg ${index !== pieRecords.length - 1 ? 'border-b border-gray-300' : ''}`}
>
<Text className="text-base font-medium text-gray-800">{item.x}</Text>
<Text className="text-base font-semibold text-gray-700">
{item.y}
{' '}
元
</Text>
</View>
))}
</View>
)
: (
<EmptyState title="空" />
)
}
</View>
{
timeType === 2
? (
<ADateRangePicker
show={showDatePicker}
onConfirm={(startDate, endDate) => {
setStartDate(startDate)
setEndDate(endDate)
setShowDatePicker(false)
}}
onClose={() => {
setShowDatePicker(false)
}}
/>
)
: (
<ADatePicker
show={showDatePicker}
mode={timeType === 0 ? 'month' : 'year'}
onConfirm={(val) => {
setStartDate(val)
setShowDatePicker(false)
}}
onClose={() => {
setShowDatePicker(false)
}}
/>
)
}
</ScrollView>
)
}
@@ -112,12 +264,12 @@ export default function Analysis() {
{
key: 'expense',
title: '支出',
element: <AnalysisContent analysisType="expense" />,
element: <AnalysisContent analysisType={MoneyCategoryEnum.Expense} />,
},
{
key: 'income',
title: '收入',
element: <AnalysisContent analysisType="income" />,
element: <AnalysisContent analysisType={MoneyCategoryEnum.Income} />,
},
]}
/>
@@ -0,0 +1,89 @@
import type { IDateRangePickerProps } from '@yz1311/react-native-wheel-picker'
import { DateRangePicker } from '@yz1311/react-native-wheel-picker'
import dayjs from 'dayjs'
import { useEffect, useRef, useState } from 'react'
import { Animated, Modal, StyleSheet, TouchableOpacity } from 'react-native'
interface ADateRangePickerProps extends IDateRangePickerProps {
show?: boolean
onClose?: () => void
onConfirm?: (startDate: string, endDate: string) => void
}
export default function ADateRangePicker({
show,
onClose,
onConfirm,
...restProps
}: ADateRangePickerProps) {
const fadeAnim = useRef(new Animated.Value(0)).current
const slideAnim = useRef(new Animated.Value(300)).current
const [visible, setVisible] = useState(show)
useEffect(() => {
if (show) {
setVisible(true)
fadeAnim.setValue(0)
slideAnim.setValue(300)
Animated.parallel([
Animated.timing(fadeAnim, { toValue: 1, duration: 250, useNativeDriver: true }),
Animated.timing(slideAnim, { toValue: 0, duration: 250, useNativeDriver: true }),
]).start()
}
else {
Animated.parallel([
Animated.timing(fadeAnim, { toValue: 0, duration: 250, useNativeDriver: true }),
Animated.timing(slideAnim, { toValue: 280, duration: 250, useNativeDriver: true }),
]).start(() => setVisible(false))
}
}, [show])
const handleCancel = () => onClose?.()
const handleConfirm = (startDate: Date, endDate: Date) => {
const formattedStartDate = dayjs(startDate).format('YYYY-MM-DD')
const formattedEndDate = dayjs(endDate).format('YYYY-MM-DD')
onConfirm?.(formattedStartDate, formattedEndDate)
}
if (!visible)
return null
return (
<Modal transparent visible={visible} animationType="none">
<Animated.View style={[styles.overlay, { opacity: fadeAnim }]}>
<TouchableOpacity style={{ flex: 1 }} activeOpacity={1} onPress={handleCancel} />
</Animated.View>
<Animated.View
style={[styles.pickerContainer, { transform: [{ translateY: slideAnim }] }]}
>
<DateRangePicker
pickerTitle="时间范围"
pickerCancelBtnText="取消"
pickerConfirmBtnText="确认"
{...restProps}
isModal={false}
onPickerCancel={handleCancel}
onPickerConfirm={handleConfirm}
/>
</Animated.View>
</Modal>
)
}
const styles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)',
},
pickerContainer: {
backgroundColor: '#fff',
overflow: 'hidden',
position: 'absolute',
bottom: 0,
width: '100%',
},
})
@@ -2,8 +2,9 @@ import { Avatar } from './Avatar'
import Button from './Button'
import { Cell, CellGroup } from './Cell'
import Checkbox from './Checkbox'
import DatePicker from './DatePicker'
import DatePicker from './DatePicker'
import DateRangePicker from './DateRangePicker'
import Divider from './Divider'
import Drawer from './Drawer'
import Form, { FormRef } from './Form'
@@ -13,7 +14,6 @@ import ListItem from './ListItem'
import LucideIcon from './LucideIcon'
import Modal from './Modal'
import SegmentedTabs from './SegmentedTabs'
import Tab from './Tab'
export {
@@ -23,6 +23,7 @@ export {
CellGroup as ACellGroup,
Checkbox as ACheckbox,
DatePicker as ADatePicker,
DateRangePicker as ADateRangePicker,
Divider as ADivider,
Drawer as ADrawer,
Form as AForm,
@@ -0,0 +1,13 @@
import type { AnalysisMoneyLineResult, AnalysisMoneyParams, AnalysisMoneyPieResult } from '@/types/money/analysis'
import alovaInstance from '../../index'
const API_PREFIX = '/money/analysis'
export function analysisMoneyLineApi(params: AnalysisMoneyParams) {
return alovaInstance.Post<ResponseData<AnalysisMoneyLineResult>>(`${API_PREFIX}/line`, params)
}
export function analysisMoneyPieApi(params: AnalysisMoneyParams) {
return alovaInstance.Post<ResponseData<AnalysisMoneyPieResult>>(`${API_PREFIX}/pie`, params)
}
+2 -2
View File
@@ -28,8 +28,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app',
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
@@ -0,0 +1,19 @@
export interface AnalysisMoneyParams {
start_date: string
end_date: string
type: number
period_type: number // 0 = 日分析, 1 = 月分析, 2 = 年分析
}
export interface AnalysisMoneyAmount {
x: string
y: string
}
export interface AnalysisMoneyLineResult {
records: AnalysisMoneyAmount[]
}
export interface AnalysisMoneyPieResult {
records: AnalysisMoneyAmount[]
}
+14
View File
@@ -28,6 +28,10 @@ export function getCurrentYearMonth(format = 'YYYY-MM'): string {
return dayjs().format(format)
}
export function getCurrentYear(): string {
return dayjs().format('YYYY')
}
/** 字符串 / 数字 / Date → Date(无效返回 null) */
export function toDate(input: string | number | Date | undefined | null): Date {
if (!input)
@@ -105,3 +109,13 @@ export function formatRelativeTime(date: string | Date | number): string {
return `${years}年前`
}
}
// 获取本月的第一天
export function getStartOfMonth() {
return dayjs().startOf('month').format('YYYY-MM-DD')
}
// 获取本月的最后一天
export function getEndOfMonth() {
return dayjs().endOf('month').format('YYYY-MM-DD')
}
+80 -73
View File
@@ -1,87 +1,94 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>财务分析 Demo</title>
<script src="https://cdn.tailwindcss.com"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Test</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
#output {
margin-top: 20px;
padding: 10px;
border: 1px solid #ccc;
min-height: 150px;
}
#sendMessage {
margin-top: 20px;
padding: 10px;
width: 100%;
}
</style>
</head>
<body class="bg-gray-100 p-4">
<body>
<!-- Header: 收支切换 -->
<div class="flex justify-center gap-4 mb-4">
<button id="expenseBtn" class="px-4 py-2 rounded-full bg-blue-500 text-white font-semibold">支出</button>
<button id="incomeBtn" class="px-4 py-2 rounded-full bg-gray-200 text-gray-700 font-semibold">收入</button>
</div>
<!-- 时间维度选择 -->
<div class="flex justify-center gap-2 mb-4">
<button class="timeBtn px-3 py-1 rounded-full bg-blue-500 text-white font-medium" data-type="month">按月</button>
<button class="timeBtn px-3 py-1 rounded-full bg-gray-200 text-gray-700 font-medium" data-type="year">按年</button>
<button class="timeBtn px-3 py-1 rounded-full bg-gray-200 text-gray-700 font-medium" data-type="custom">自定义</button>
</div>
<!-- 日期选择 -->
<div id="datePickerContainer" class="flex justify-center mb-4">
<!-- 默认显示按月日期选择 -->
<input type="month" id="monthInput" class="p-2 rounded border border-gray-300">
<input type="month" id="yearInput" class="p-2 rounded border border-gray-300 hidden">
<div id="customInput" class="flex gap-2 hidden">
<input type="date" id="startDate" class="p-2 rounded border border-gray-300">
<span class="self-center">~</span>
<input type="date" id="endDate" class="p-2 rounded border border-gray-300">
<h2>WebSocket Test</h2>
<div>
<label for="messageInput">Enter Message:</label>
<input type="text" id="messageInput" placeholder="Type your message here">
<button id="sendBtn">Send Message</button>
</div>
</div>
<!-- 示例图表区域 -->
<div class="bg-white p-4 rounded shadow text-center">
<p class="text-gray-500">这里显示图表或分析结果</p>
</div>
<div id="output">
<h3>Messages:</h3>
<div id="messages"></div>
</div>
<script>
const expenseBtn = document.getElementById('expenseBtn')
const incomeBtn = document.getElementById('incomeBtn')
const timeBtns = document.querySelectorAll('.timeBtn')
const monthInput = document.getElementById('monthInput')
const yearInput = document.getElementById('yearInput')
const customInput = document.getElementById('customInput')
<script>
// Create a new WebSocket connection
const socket = new WebSocket('ws://192.168.1.137:8086/app/ws');
// 收支切换逻辑
expenseBtn.addEventListener('click', () => {
expenseBtn.classList.add('bg-blue-500', 'text-white')
expenseBtn.classList.remove('bg-gray-200', 'text-gray-700')
incomeBtn.classList.add('bg-gray-200', 'text-gray-700')
incomeBtn.classList.remove('bg-blue-500', 'text-white')
})
// Get HTML elements
const sendButton = document.getElementById('sendBtn');
const messageInput = document.getElementById('messageInput');
const messagesDiv = document.getElementById('messages');
incomeBtn.addEventListener('click', () => {
incomeBtn.classList.add('bg-blue-500', 'text-white')
incomeBtn.classList.remove('bg-gray-200', 'text-gray-700')
expenseBtn.classList.add('bg-gray-200', 'text-gray-700')
expenseBtn.classList.remove('bg-blue-500', 'text-white')
})
// WebSocket onopen event: Fired when WebSocket connection is established
socket.onopen = function(event) {
console.log("Connected to WebSocket server!");
displayMessage("Connected to WebSocket server!");
};
// 时间维度切换逻辑
timeBtns.forEach(btn => {
btn.addEventListener('click', () => {
timeBtns.forEach(b => {
b.classList.remove('bg-blue-500', 'text-white')
b.classList.add('bg-gray-200', 'text-gray-700')
})
btn.classList.add('bg-blue-500', 'text-white')
btn.classList.remove('bg-gray-200', 'text-gray-700')
// WebSocket onmessage event: Fired when a message is received from the server
socket.onmessage = function(event) {
console.log("Received message from server: " + event.data);
displayMessage("Server: " + event.data);
};
// 显示对应日期选择控件
const type = btn.dataset.type
monthInput.classList.add('hidden')
yearInput.classList.add('hidden')
customInput.classList.add('hidden')
// WebSocket onerror event: Fired when there's an error with the WebSocket
socket.onerror = function(error) {
console.log(error)
console.error("WebSocket Error: " + error);
displayMessage("WebSocket Error: " + error);
};
if (type === 'month') monthInput.classList.remove('hidden')
if (type === 'year') yearInput.classList.remove('hidden')
if (type === 'custom') customInput.classList.remove('hidden')
})
})
</script>
// WebSocket onclose event: Fired when the connection is closed
socket.onclose = function(event) {
console.log("WebSocket connection closed");
displayMessage("WebSocket connection closed");
};
// Function to display messages on the page
function displayMessage(message) {
const messageElement = document.createElement('div');
messageElement.textContent = message;
messagesDiv.appendChild(messageElement);
}
// Button to send the message
sendButton.onclick = function() {
const message = messageInput.value;
if (message) {
console.log("Sending message: " + message);
socket.send(message);
displayMessage("You: " + message);
messageInput.value = ''; // Clear the input field
} else {
alert("Please enter a message!");
}
};
</script>
</body>
</html>