u
This commit is contained in:
1 parent
bce259f165
commit
dc46d8034d
8 files changed
+407
-236
No files matched your search
@@ -56,6 +56,8 @@ func ListMoneyRecords(ctx context.Context, c *app.RequestContext) {
|
|||||||
|
|
||||||
userID := middleware.GetUserID(ctx)
|
userID := middleware.GetUserID(ctx)
|
||||||
|
|
||||||
|
categoryId := req.CategoryId
|
||||||
|
|
||||||
// 查询账目列表(统一 SQL)
|
// 查询账目列表(统一 SQL)
|
||||||
dbRecords, err := db.Queries.ListMoneyRecords(
|
dbRecords, err := db.Queries.ListMoneyRecords(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -65,6 +67,7 @@ func ListMoneyRecords(ctx context.Context, c *app.RequestContext) {
|
|||||||
RecordTime_2: pgtypex.TimePtrToPgDate(endTime),
|
RecordTime_2: pgtypex.TimePtrToPgDate(endTime),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorx.AddError(c, err)
|
errorx.AddError(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ type ListMoneyRecordsParams struct {
|
|||||||
|
|
||||||
StartTime string `protobuf:"bytes,1,opt,name=StartTime,proto3" form:"start_time" json:"start_time"`
|
StartTime string `protobuf:"bytes,1,opt,name=StartTime,proto3" form:"start_time" json:"start_time"`
|
||||||
EndTime string `protobuf:"bytes,2,opt,name=EndTime,proto3" form:"end_time" json:"end_time"`
|
EndTime string `protobuf:"bytes,2,opt,name=EndTime,proto3" form:"end_time" json:"end_time"`
|
||||||
|
CategoryId int64 `protobuf:"varint,3,opt,name=CategoryId,proto3" form:"category_id" json:"category_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListMoneyRecordsParams) Reset() {
|
func (x *ListMoneyRecordsParams) Reset() {
|
||||||
@@ -189,6 +190,13 @@ func (x *ListMoneyRecordsParams) GetEndTime() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ListMoneyRecordsParams) GetCategoryId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.CategoryId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// 查询记账记录结果
|
// 查询记账记录结果
|
||||||
type ListMoneyRecordsResult struct {
|
type ListMoneyRecordsResult struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@@ -582,63 +590,66 @@ var file_money_money_record_types_proto_rawDesc = []byte{
|
|||||||
0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x08, 0x55,
|
0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x08, 0x55,
|
||||||
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xca,
|
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c, 0xca,
|
||||||
0xbb, 0x18, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x55, 0x73, 0x65,
|
0xbb, 0x18, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x55, 0x73, 0x65,
|
||||||
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6e, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e,
|
0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f,
|
||||||
0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12,
|
0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73,
|
||||||
0x2c, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
|
0x12, 0x2c, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||||
0x28, 0x09, 0x42, 0x0e, 0xca, 0xbb, 0x18, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69,
|
0x01, 0x28, 0x09, 0x42, 0x0e, 0xca, 0xbb, 0x18, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74,
|
||||||
0x6d, 0x65, 0x52, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a,
|
0x69, 0x6d, 0x65, 0x52, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26,
|
||||||
0x07, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0c,
|
0x0a, 0x07, 0x45, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
|
||||||
0xca, 0xbb, 0x18, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x07, 0x45, 0x6e,
|
0x0c, 0xca, 0xbb, 0x18, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x07, 0x45,
|
||||||
0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x53, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x6f, 0x6e,
|
0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x2f, 0x0a, 0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
|
||||||
0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12,
|
0x72, 0x79, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0f, 0xca, 0xbb, 0x18, 0x0b,
|
||||||
0x39, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
|
0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x52, 0x0a, 0x43, 0x61, 0x74,
|
||||||
0x32, 0x12, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65,
|
0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x22, 0x53, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4d,
|
||||||
0x63, 0x6f, 0x72, 0x64, 0x42, 0x0b, 0xca, 0xbb, 0x18, 0x07, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c,
|
||||||
0x73, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0xa4, 0x02, 0x0a, 0x17, 0x49,
|
0x74, 0x12, 0x39, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03,
|
||||||
0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x2e, 0x4d, 0x6f, 0x6e, 0x65, 0x79,
|
||||||
0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x38, 0x0a, 0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
|
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x42, 0x0b, 0xca, 0xbb, 0x18, 0x07, 0x72, 0x65, 0x63, 0x6f,
|
||||||
0x72, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x18, 0xca, 0xbb, 0x18, 0x0b,
|
0x72, 0x64, 0x73, 0x52, 0x07, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x22, 0xa4, 0x02, 0x0a,
|
||||||
0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0xda, 0xbb, 0x18, 0x05, 0x24,
|
0x17, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f,
|
||||||
0x21, 0x3d, 0x27, 0x27, 0x52, 0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64,
|
0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x38, 0x0a, 0x0a, 0x43, 0x61, 0x74, 0x65,
|
||||||
0x12, 0x4d, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x18, 0xca, 0xbb,
|
||||||
0x42, 0x35, 0xca, 0xbb, 0x18, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0xda, 0xbb, 0x18, 0x27,
|
0x18, 0x0b, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0xda, 0xbb, 0x18,
|
||||||
0x24, 0x21, 0x3d, 0x27, 0x27, 0x20, 0x26, 0x26, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x28,
|
0x05, 0x24, 0x21, 0x3d, 0x27, 0x27, 0x52, 0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
|
||||||
0x27, 0x5e, 0x5c, 0x64, 0x2b, 0x28, 0x5c, 0x2e, 0x5c, 0x64, 0x7b, 0x31, 0x2c, 0x32, 0x7d, 0x29,
|
0x49, 0x64, 0x12, 0x4d, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x3f, 0x24, 0x27, 0x2c, 0x20, 0x24, 0x29, 0x52, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12,
|
0x28, 0x09, 0x42, 0x35, 0xca, 0xbb, 0x18, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0xda, 0xbb,
|
||||||
0x22, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42,
|
0x18, 0x27, 0x24, 0x21, 0x3d, 0x27, 0x27, 0x20, 0x26, 0x26, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78,
|
||||||
|
0x70, 0x28, 0x27, 0x5e, 0x5c, 0x64, 0x2b, 0x28, 0x5c, 0x2e, 0x5c, 0x64, 0x7b, 0x31, 0x2c, 0x32,
|
||||||
|
0x7d, 0x29, 0x3f, 0x24, 0x27, 0x2c, 0x20, 0x24, 0x29, 0x52, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e,
|
||||||
|
0x74, 0x12, 0x22, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x42, 0x0a, 0xca, 0xbb, 0x18, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x52, 0x06, 0x52,
|
||||||
|
0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x5c, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54,
|
||||||
|
0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3c, 0xca, 0xbb, 0x18, 0x0b, 0x72,
|
||||||
|
0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0xda, 0xbb, 0x18, 0x29, 0x24, 0x21,
|
||||||
|
0x3d, 0x27, 0x27, 0x20, 0x26, 0x26, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x28, 0x27, 0x5e,
|
||||||
|
0x5c, 0x64, 0x7b, 0x34, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32,
|
||||||
|
0x7d, 0x24, 0x27, 0x2c, 0x20, 0x24, 0x29, 0x52, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54,
|
||||||
|
0x69, 0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x6f, 0x6e,
|
||||||
|
0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xe3,
|
||||||
|
0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65,
|
||||||
|
0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1e, 0x0a, 0x02, 0x49, 0x64,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0e, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0xda, 0xbb,
|
||||||
|
0x18, 0x04, 0x24, 0x21, 0x3d, 0x30, 0x52, 0x02, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x43, 0x61,
|
||||||
|
0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0f,
|
||||||
|
0xca, 0xbb, 0x18, 0x0b, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x52,
|
||||||
|
0x0a, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x06, 0x41,
|
||||||
|
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xca, 0xbb, 0x18,
|
||||||
|
0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12,
|
||||||
|
0x22, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42,
|
||||||
0x0a, 0xca, 0xbb, 0x18, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x52, 0x06, 0x52, 0x65, 0x6d,
|
0x0a, 0xca, 0xbb, 0x18, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x52, 0x06, 0x52, 0x65, 0x6d,
|
||||||
0x61, 0x72, 0x6b, 0x12, 0x5c, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x69, 0x6d,
|
0x61, 0x72, 0x6b, 0x12, 0x2f, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x69, 0x6d,
|
||||||
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x3c, 0xca, 0xbb, 0x18, 0x0b, 0x72, 0x65, 0x63,
|
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0f, 0xca, 0xbb, 0x18, 0x0b, 0x72, 0x65, 0x63,
|
||||||
0x6f, 0x72, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0xda, 0xbb, 0x18, 0x29, 0x24, 0x21, 0x3d, 0x27,
|
0x6f, 0x72, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64,
|
||||||
0x27, 0x20, 0x26, 0x26, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x28, 0x27, 0x5e, 0x5c, 0x64,
|
0x54, 0x69, 0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f,
|
||||||
0x7b, 0x34, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x2d, 0x5c, 0x64, 0x7b, 0x32, 0x7d, 0x24,
|
0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22,
|
||||||
0x27, 0x2c, 0x20, 0x24, 0x29, 0x52, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x69, 0x6d,
|
0x39, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65,
|
||||||
0x65, 0x22, 0x19, 0x0a, 0x17, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x6f, 0x6e, 0x65, 0x79,
|
0x63, 0x6f, 0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1e, 0x0a, 0x02, 0x49, 0x64,
|
||||||
0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xe3, 0x01, 0x0a,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0e, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0xda, 0xbb,
|
||||||
0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f,
|
0x18, 0x04, 0x24, 0x21, 0x3d, 0x30, 0x52, 0x02, 0x49, 0x64, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65,
|
||||||
0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01,
|
0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52,
|
||||||
0x20, 0x01, 0x28, 0x03, 0x42, 0x0e, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0xda, 0xbb, 0x18, 0x04,
|
0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x61, 0x6c, 0x6c, 0x61, 0x70, 0x70, 0x2f,
|
||||||
0x24, 0x21, 0x3d, 0x30, 0x52, 0x02, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x0a, 0x43, 0x61, 0x74, 0x65,
|
0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x6e, 0x65, 0x79, 0x62,
|
||||||
0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0f, 0xca, 0xbb,
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x18, 0x0b, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x52, 0x0a, 0x43,
|
|
||||||
0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x06, 0x41, 0x6d, 0x6f,
|
|
||||||
0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xca, 0xbb, 0x18, 0x06, 0x61,
|
|
||||||
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x22, 0x0a,
|
|
||||||
0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xca,
|
|
||||||
0xbb, 0x18, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x52, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72,
|
|
||||||
0x6b, 0x12, 0x2f, 0x0a, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18,
|
|
||||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0f, 0xca, 0xbb, 0x18, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x72,
|
|
||||||
0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x0a, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x54, 0x69,
|
|
||||||
0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65,
|
|
||||||
0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x39, 0x0a,
|
|
||||||
0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f,
|
|
||||||
0x72, 0x64, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1e, 0x0a, 0x02, 0x49, 0x64, 0x18, 0x01,
|
|
||||||
0x20, 0x01, 0x28, 0x03, 0x42, 0x0e, 0xca, 0xbb, 0x18, 0x02, 0x69, 0x64, 0xda, 0xbb, 0x18, 0x04,
|
|
||||||
0x24, 0x21, 0x3d, 0x30, 0x52, 0x02, 0x49, 0x64, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65,
|
|
||||||
0x74, 0x65, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73,
|
|
||||||
0x75, 0x6c, 0x74, 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 (
|
var (
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ message MoneyRecord {
|
|||||||
message ListMoneyRecordsParams {
|
message ListMoneyRecordsParams {
|
||||||
string StartTime = 1 [(api.body)="start_time"];
|
string StartTime = 1 [(api.body)="start_time"];
|
||||||
string EndTime = 2 [(api.body)="end_time"];
|
string EndTime = 2 [(api.body)="end_time"];
|
||||||
|
int64 CategoryId = 3 [(api.body)="category_id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询记账记录结果
|
// 查询记账记录结果
|
||||||
|
|||||||
@@ -40,101 +40,65 @@ func Exists(key string) (bool, error) {
|
|||||||
return cnt > 0, nil
|
return cnt > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserSpaces 初始化用户空间列表(spaceID -> role)
|
// ================== Space User / Role ==================
|
||||||
func SetUserSpaces(ctx context.Context, userID int64, spaces map[int64]int32) error {
|
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
|
||||||
|
|
||||||
pipe := Client().TxPipeline()
|
const (
|
||||||
|
RoleMember int32 = 1
|
||||||
|
RoleAdmin int32 = 5
|
||||||
|
RoleOwner int32 = 9
|
||||||
|
|
||||||
// 1. 先清空旧数据
|
SpaceUserTTL = 10 * time.Minute
|
||||||
pipe.Del(ctx, key)
|
)
|
||||||
|
|
||||||
// 2. 再写入新数据(如果有)
|
// space:user:{spaceId}:{userId}
|
||||||
if len(spaces) > 0 {
|
func SpaceUserKey(spaceId int64, userId int64) string {
|
||||||
data := make(map[string]interface{}, len(spaces))
|
return fmt.Sprintf("space:user:%d:%d", spaceId, userId)
|
||||||
for spaceID, role := range spaces {
|
|
||||||
data[strconv.FormatInt(spaceID, 10)] = role
|
|
||||||
}
|
|
||||||
pipe.HSet(ctx, key, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 执行
|
|
||||||
_, err := pipe.Exec(ctx)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOrUpdateUserSpace 添加或更新单个空间的角色(增量更新)
|
// Set / Create space user role
|
||||||
func AddOrUpdateUserSpace(ctx context.Context, userID, spaceID int64, role int32) error {
|
func SetSpaceUserRole(spaceId int64, userId int64, role int32) error {
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
key := SpaceUserKey(spaceId, userId)
|
||||||
return Client().HSet(ctx, key, strconv.FormatInt(spaceID, 10), role).Err()
|
return Set(key, role, SpaceUserTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserSpaces 获取用户所有空间列表和角色
|
// Update space user role (same as set, semantic clarity)
|
||||||
func GetUserSpaces(ctx context.Context, userID int64) (map[int64]int, error) {
|
func UpdateSpaceUserRole(spaceId int64, userId int64, role int32) error {
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
return SetSpaceUserRole(spaceId, userId, role)
|
||||||
result, err := Client().HGetAll(ctx, key).Result()
|
}
|
||||||
|
|
||||||
|
// Get space user role
|
||||||
|
// return 0 means not exists
|
||||||
|
func GetSpaceUserRole(spaceId int64, userId int64) (int32, error) {
|
||||||
|
key := SpaceUserKey(spaceId, userId)
|
||||||
|
val, err := Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return 0, err
|
||||||
|
}
|
||||||
|
if val == "" {
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
spaces := make(map[int64]int, len(result))
|
r, err := strconv.ParseInt(val, 10, 32)
|
||||||
for k, v := range result {
|
|
||||||
spaceID, err1 := strconv.ParseInt(k, 10, 64)
|
|
||||||
role, err2 := strconv.Atoi(v)
|
|
||||||
if err1 != nil || err2 != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
spaces[spaceID] = role
|
|
||||||
}
|
|
||||||
|
|
||||||
return spaces, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserSpaceRole 获取用户在指定空间的角色
|
|
||||||
func GetUserSpaceRole(ctx context.Context, userID int64, spaceID int64) (role int, exists bool, err error) {
|
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
|
||||||
val, err := Client().HGet(ctx, key, strconv.FormatInt(spaceID, 10)).Result()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, redis.Nil) {
|
return 0, err
|
||||||
return 0, false, nil // 用户不在空间
|
|
||||||
}
|
}
|
||||||
return 0, false, err
|
return int32(r), nil
|
||||||
}
|
|
||||||
|
|
||||||
role, err = strconv.Atoi(val)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return role, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveUserSpace 从用户缓存中删除一个空间(用户退出或被踢)
|
// Delete space user (kick / dissolve)
|
||||||
func RemoveUserSpace(ctx context.Context, userID int64, spaceID int64) error {
|
func DeleteSpaceUser(spaceId int64, userId int64) error {
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
key := SpaceUserKey(spaceId, userId)
|
||||||
return Client().HDel(ctx, key, strconv.FormatInt(spaceID, 10)).Err()
|
return Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearUserSpaces 删除用户的所有空间列表
|
// Exists space user
|
||||||
func ClearUserSpaces(ctx context.Context, userID int64) error {
|
func ExistsSpaceUser(spaceId int64, userId int64) (bool, error) {
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
key := SpaceUserKey(spaceId, userId)
|
||||||
return Client().Del(ctx, key).Err()
|
return Exists(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InSpace(ctx context.Context, userID, spaceID int64) (bool, error) {
|
// Permission check
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
// owner > admin > member
|
||||||
return Client().HExists(ctx, key, strconv.FormatInt(spaceID, 10)).Result()
|
func HasPermission(userRole int32, needRole int32) bool {
|
||||||
}
|
return userRole >= needRole
|
||||||
|
|
||||||
// HasUserSpaces 判断用户是否在 Redis 中有空间缓存
|
|
||||||
func HasUserSpaces(ctx context.Context, userID int64) (bool, error) {
|
|
||||||
key := fmt.Sprintf("user:%d:spaces", userID)
|
|
||||||
|
|
||||||
// HLen 返回 hash 的字段数量
|
|
||||||
cnt, err := Client().HLen(ctx, key).Result()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cnt > 0, nil
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
|
import type { DateMorePickerMode } from '@/components/common/ao/DateMorePicker'
|
||||||
import { useFocusEffect, useRouter } from 'expo-router'
|
import { useFocusEffect, useRouter } from 'expo-router'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { Image, Text, TouchableOpacity, View } from 'react-native'
|
import { Image, Text, TouchableOpacity, View } from 'react-native'
|
||||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { ADatePicker, ADrawer, AListItem, LucideIcon } from '@/components/common/ao'
|
import { ADateMorePicker, ADatePicker, ADrawer, AListItem, LucideIcon } from '@/components/common/ao'
|
||||||
|
import { resolveDateRange } from '@/components/common/ao/DateMorePicker'
|
||||||
import { Fab, FabIcon } from '@/components/ui/fab'
|
import { Fab, FabIcon } from '@/components/ui/fab'
|
||||||
import { AddIcon } from '@/components/ui/icon'
|
import { AddIcon } from '@/components/ui/icon'
|
||||||
import { deleteMoneyRecordApi, listMoneyRecordsApi } from '@/request/api/money/record'
|
import { deleteMoneyRecordApi, listMoneyRecordsApi } from '@/request/api/money/record'
|
||||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||||
import { useMoneyRecordStore } from '@/store/moneyRecord'
|
import { useMoneyRecordStore } from '@/store/moneyRecord'
|
||||||
import { getCurrentYearMonth } from '@/utils/time'
|
import { getCurrentYearMonth, normalizeDateTime } from '@/utils/time'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
import CategorySheet from './components/CategorySheet'
|
import CategorySheet from './components/CategorySheet'
|
||||||
import TransactionsList from './components/TransactionsList'
|
import TransactionsList from './components/TransactionsList'
|
||||||
@@ -22,23 +24,30 @@ export default function Money() {
|
|||||||
|
|
||||||
// 日期筛选
|
// 日期筛选
|
||||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||||
|
const [dateMode, setDateMode] = useState<DateMorePickerMode>('month')
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useState({
|
const [searchParams, setSearchParams] = useState({
|
||||||
record_time: getCurrentYearMonth(),
|
startTime: getCurrentYearMonth(),
|
||||||
|
endTime: '',
|
||||||
category: -1,
|
category: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [showCategorySheet, setShowCategorySheet] = useState(false)
|
const [showCategorySheet, setShowCategorySheet] = useState(false)
|
||||||
|
|
||||||
// 操作菜单
|
// 操作菜单
|
||||||
const [showActionsMenu, setShowActionsMenu] = useState(false)
|
const [showActionsMenu, setShowActionsMenu] = useState(false)
|
||||||
|
|
||||||
function fetchMoneyRecords() {
|
function fetchMoneyRecords() {
|
||||||
|
console.log(searchParams)
|
||||||
|
|
||||||
|
const resolveDate = resolveDateRange(dateMode, searchParams.startTime)
|
||||||
|
|
||||||
listMoneyRecordsApi({
|
listMoneyRecordsApi({
|
||||||
// record_time: searchParams.record_time,
|
start_time: resolveDate.startTime,
|
||||||
start_time: '',
|
end_time: resolveDate.endTime,
|
||||||
end_time: '',
|
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
moneyRecordStore.setRecords(res.data.records || [], searchParams.record_time)
|
moneyRecordStore.setRecords(res.data.records || [])
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toast.show({
|
toast.show({
|
||||||
@@ -52,7 +61,7 @@ export default function Money() {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
fetchMoneyRecords()
|
fetchMoneyRecords()
|
||||||
}, [searchParams.record_time]),
|
}, [searchParams.startTime, searchParams.endTime]),
|
||||||
)
|
)
|
||||||
|
|
||||||
async function handleDeleteRecord(id: number) {
|
async function handleDeleteRecord(id: number) {
|
||||||
@@ -63,6 +72,18 @@ export default function Money() {
|
|||||||
fetchMoneyRecords()
|
fetchMoneyRecords()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeModeTip() {
|
||||||
|
if (dateMode === 'unlimited') {
|
||||||
|
return '全部'
|
||||||
|
}
|
||||||
|
else if (dateMode === 'range') {
|
||||||
|
return `${searchParams.startTime} - ${searchParams.endTime}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return searchParams.startTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
@@ -80,7 +101,13 @@ export default function Money() {
|
|||||||
onPress={() => setShowDatePicker(true)}
|
onPress={() => setShowDatePicker(true)}
|
||||||
className="flex flex-row items-center gap-1"
|
className="flex flex-row items-center gap-1"
|
||||||
>
|
>
|
||||||
<Text className="font-semibold text-lg">{searchParams.record_time}</Text>
|
<Text
|
||||||
|
className="font-semibold text-lg"
|
||||||
|
numberOfLines={1}
|
||||||
|
ellipsizeMode="tail"
|
||||||
|
>
|
||||||
|
{timeModeTip()}
|
||||||
|
</Text>
|
||||||
<LucideIcon icon="ChevronDown" size={16} color="#666" clickable={false} />
|
<LucideIcon icon="ChevronDown" size={16} color="#666" clickable={false} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
@@ -182,7 +209,26 @@ export default function Money() {
|
|||||||
<FabIcon as={AddIcon} size="xl" />
|
<FabIcon as={AddIcon} size="xl" />
|
||||||
</Fab>
|
</Fab>
|
||||||
|
|
||||||
<ADatePicker
|
<ADateMorePicker
|
||||||
|
visible={showDatePicker}
|
||||||
|
mode={dateMode}
|
||||||
|
onClose={() => {
|
||||||
|
setShowDatePicker(false)
|
||||||
|
}}
|
||||||
|
startDate={searchParams.startTime}
|
||||||
|
endDate={searchParams.endTime}
|
||||||
|
hiddenModes={['datetime', 'time']}
|
||||||
|
onConfirm={(value) => {
|
||||||
|
setDateMode(value.mode)
|
||||||
|
|
||||||
|
setSearchParams(val => ({
|
||||||
|
...val,
|
||||||
|
startTime: value.startTime,
|
||||||
|
endTime: value.endTime,
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <ADatePicker
|
||||||
show={showDatePicker}
|
show={showDatePicker}
|
||||||
mode="month"
|
mode="month"
|
||||||
date={searchParams.record_time}
|
date={searchParams.record_time}
|
||||||
@@ -196,7 +242,7 @@ export default function Money() {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowDatePicker(false)
|
setShowDatePicker(false)
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<CategorySheet
|
<CategorySheet
|
||||||
open={showCategorySheet}
|
open={showCategorySheet}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Dayjs } from 'dayjs'
|
||||||
import SegmentedControl from '@react-native-segmented-control/segmented-control'
|
import SegmentedControl from '@react-native-segmented-control/segmented-control'
|
||||||
import { DatePicker, DateRangePicker } from '@yz1311/react-native-wheel-picker'
|
import { DatePicker, DateRangePicker } from '@yz1311/react-native-wheel-picker'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -11,7 +12,7 @@ import Tab from './Tab'
|
|||||||
|
|
||||||
/** ================= 类型 ================= */
|
/** ================= 类型 ================= */
|
||||||
|
|
||||||
export type PickerMode
|
export type DateMorePickerMode
|
||||||
= | 'unlimited'
|
= | 'unlimited'
|
||||||
| 'date'
|
| 'date'
|
||||||
| 'month'
|
| 'month'
|
||||||
@@ -20,22 +21,83 @@ export type PickerMode
|
|||||||
| 'datetime'
|
| 'datetime'
|
||||||
| 'range'
|
| 'range'
|
||||||
|
|
||||||
type PickerModeInternal = PickerMode | 'custom'
|
type PickerModeInternal = DateMorePickerMode | 'custom'
|
||||||
type TabKey = 'custom' | PickerMode
|
type TabKey = 'custom' | DateMorePickerMode
|
||||||
type DatePickerMode = 'date' | 'month' | 'year' | 'time' | 'datetime'
|
type DatePickerMode = 'date' | 'month' | 'year' | 'time' | 'datetime'
|
||||||
|
|
||||||
interface DateMorePickerProps {
|
interface DateMorePickerProps {
|
||||||
mode?: PickerMode
|
mode?: DateMorePickerMode
|
||||||
startDate?: Date | string
|
startDate?: Date | string
|
||||||
endDate?: Date | string
|
endDate?: Date | string
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
onConfirm?: (value: {
|
onConfirm?: (value: {
|
||||||
mode: PickerMode
|
mode: DateMorePickerMode
|
||||||
startTime: string
|
startTime: string
|
||||||
endTime: string
|
endTime: string
|
||||||
}) => void
|
}) => void
|
||||||
onChange?: (mode: PickerMode) => void
|
hiddenModes?: DateMorePickerMode[]
|
||||||
|
onChange?: (mode: DateMorePickerMode) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DateRangeResult {
|
||||||
|
startTime: string
|
||||||
|
endTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveDateRange(
|
||||||
|
dateMode: DateMorePickerMode,
|
||||||
|
baseDate?: string | Date | Dayjs,
|
||||||
|
): DateRangeResult {
|
||||||
|
const now = dayjs()
|
||||||
|
const base = baseDate ? dayjs(baseDate) : now
|
||||||
|
|
||||||
|
// 兜底非法时间
|
||||||
|
if (!base.isValid()) {
|
||||||
|
return { startTime: '', endTime: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
let start: Dayjs
|
||||||
|
let end: Dayjs
|
||||||
|
|
||||||
|
switch (dateMode) {
|
||||||
|
case 'unlimited':
|
||||||
|
start = dayjs('1970-01-01 00:00:00')
|
||||||
|
end = now.add(10, 'year').endOf('day')
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'date':
|
||||||
|
start = base.startOf('day')
|
||||||
|
end = base.endOf('day')
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'month':
|
||||||
|
start = base.startOf('month')
|
||||||
|
end = base.endOf('month')
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'year':
|
||||||
|
start = base.startOf('year')
|
||||||
|
end = base.endOf('year')
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'time':
|
||||||
|
case 'datetime':
|
||||||
|
start = base
|
||||||
|
end = base
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'range':
|
||||||
|
default:
|
||||||
|
start = base.startOf('day')
|
||||||
|
end = base.endOf('day')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startTime: start.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
endTime: end.format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DateMorePicker({
|
export default function DateMorePicker({
|
||||||
@@ -46,100 +108,85 @@ export default function DateMorePicker({
|
|||||||
onClose,
|
onClose,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onChange,
|
onChange,
|
||||||
|
hiddenModes = [],
|
||||||
}: DateMorePickerProps) {
|
}: DateMorePickerProps) {
|
||||||
const [localVisible, setLocalVisible] = useState(false)
|
const [localVisible, setLocalVisible] = useState(false)
|
||||||
|
|
||||||
const coreModes: DatePickerMode[] = ['date', 'month', 'year', 'time', 'datetime']
|
|
||||||
const coreLabels = ['日', '月', '年', '时间', '日期']
|
|
||||||
|
|
||||||
/** ================= 内部编辑态 ================= */
|
/** ================= 内部编辑态 ================= */
|
||||||
|
|
||||||
const [internalMode, setInternalMode] = useState<PickerMode>('date')
|
const [internalMode, setInternalMode] = useState<DateMorePickerMode>(mode || 'date')
|
||||||
const [internalStart, setInternalStart] = useState<Date | string>(getCurrentDate())
|
const [internalStart, setInternalStart] = useState<Date | string>(startDate || getCurrentDate())
|
||||||
const [internalEnd, setInternalEnd] = useState<Date | string>(getCurrentDate())
|
const [internalEnd, setInternalEnd] = useState<Date | string>(endDate || getCurrentDate())
|
||||||
const [activeTab, setActiveTab] = useState<TabKey>('custom')
|
const [activeTab, setActiveTab] = useState<TabKey>('custom')
|
||||||
|
|
||||||
/** ================= 重置编辑态(关键) ================= */
|
/** ================= 重置编辑态(关键) ================= */
|
||||||
|
|
||||||
const resetInternalState = () => {
|
useEffect(() => {
|
||||||
setInternalMode(mode || 'date')
|
if (visible) {
|
||||||
setInternalStart(startDate ?? getCurrentDate())
|
if (mode === 'unlimited') {
|
||||||
setInternalEnd(endDate ?? getCurrentDate())
|
setActiveTab('unlimited')
|
||||||
|
}
|
||||||
|
else if (mode === 'range') {
|
||||||
|
setActiveTab('range')
|
||||||
|
}
|
||||||
|
else {
|
||||||
setActiveTab('custom')
|
setActiveTab('custom')
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
resetInternalState()
|
|
||||||
setLocalVisible(true)
|
setLocalVisible(true)
|
||||||
}
|
}
|
||||||
}, [visible])
|
}, [visible])
|
||||||
|
|
||||||
/** ================= 快速选择逻辑(修复时间归零) ================= */
|
/** ================= 快速选择逻辑(修复时间归零) ================= */
|
||||||
|
function getDefaultDateByMode(mode: DatePickerMode, now = dayjs()) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'date':
|
||||||
|
return now.startOf('day')
|
||||||
|
case 'month':
|
||||||
|
return now.startOf('month')
|
||||||
|
case 'year':
|
||||||
|
return now.startOf('year')
|
||||||
|
case 'time':
|
||||||
|
return now.second(0)
|
||||||
|
case 'datetime':
|
||||||
|
return now
|
||||||
|
default:
|
||||||
|
return now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleModeChange = (next: PickerModeInternal) => {
|
const handleModeChange = (next: PickerModeInternal) => {
|
||||||
const now = dayjs()
|
const now = dayjs()
|
||||||
|
|
||||||
switch (next) {
|
if (next === 'unlimited') {
|
||||||
case 'custom':
|
|
||||||
setInternalMode('date')
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'unlimited':
|
|
||||||
setInternalStart('')
|
setInternalStart('')
|
||||||
setInternalEnd('')
|
setInternalEnd('')
|
||||||
setInternalMode('unlimited')
|
setInternalMode('unlimited')
|
||||||
break
|
onChange?.('unlimited')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case 'range': {
|
if (next === 'range') {
|
||||||
const base = now.startOf('day')
|
const base = now.startOf('day')
|
||||||
setInternalStart(base.toDate())
|
setInternalStart(base.toDate())
|
||||||
setInternalEnd(base.toDate())
|
setInternalEnd(base.toDate())
|
||||||
setInternalMode('range')
|
setInternalMode('range')
|
||||||
break
|
onChange?.('range')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'date': {
|
const targetMode: DatePickerMode
|
||||||
const base = now.startOf('day')
|
= next === 'custom'
|
||||||
|
? visibleCoreModeOptions[0].mode
|
||||||
|
: next
|
||||||
|
|
||||||
|
const base = getDefaultDateByMode(targetMode, now)
|
||||||
|
|
||||||
setInternalStart(base.toDate())
|
setInternalStart(base.toDate())
|
||||||
setInternalEnd(base.toDate())
|
setInternalEnd(base.toDate())
|
||||||
setInternalMode('date')
|
setInternalMode(targetMode)
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'month': {
|
onChange?.(targetMode)
|
||||||
const base = now.startOf('month')
|
|
||||||
setInternalStart(base.toDate())
|
|
||||||
setInternalEnd(base.toDate())
|
|
||||||
setInternalMode('month')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'year': {
|
|
||||||
const base = now.startOf('year')
|
|
||||||
setInternalStart(base.toDate())
|
|
||||||
setInternalEnd(base.toDate())
|
|
||||||
setInternalMode('year')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'time': {
|
|
||||||
const base = now.second(0)
|
|
||||||
setInternalStart(base.toDate())
|
|
||||||
setInternalEnd(base.toDate())
|
|
||||||
setInternalMode('time')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'datetime': {
|
|
||||||
setInternalStart(now.toDate())
|
|
||||||
setInternalEnd(now.toDate())
|
|
||||||
setInternalMode('datetime')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange?.(next === 'custom' ? 'date' : next)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -149,7 +196,7 @@ export default function DateMorePicker({
|
|||||||
|
|
||||||
/** ================= 确认(提交) ================= */
|
/** ================= 确认(提交) ================= */
|
||||||
|
|
||||||
const formatMap: Record<PickerMode, string> = {
|
const formatMap: Record<DateMorePickerMode, string> = {
|
||||||
unlimited: '',
|
unlimited: '',
|
||||||
range: 'YYYY-MM-DD',
|
range: 'YYYY-MM-DD',
|
||||||
date: 'YYYY-MM-DD',
|
date: 'YYYY-MM-DD',
|
||||||
@@ -189,6 +236,73 @@ export default function DateMorePicker({
|
|||||||
handleClose()
|
handleClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isHidden = (mode: DateMorePickerMode) =>
|
||||||
|
hiddenModes.includes(mode)
|
||||||
|
|
||||||
|
const tabItems: { label: string, key: TabKey }[] = [
|
||||||
|
{ label: '自定义', key: 'custom' },
|
||||||
|
{ label: '不限', key: 'unlimited' },
|
||||||
|
{ label: '范围', key: 'range' },
|
||||||
|
{ label: '今天', key: 'date' },
|
||||||
|
{ label: '本月', key: 'month' },
|
||||||
|
{ label: '本年', key: 'year' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const visibleTabItems = tabItems.filter((item) => {
|
||||||
|
if (item.key === 'unlimited' && isHidden('unlimited'))
|
||||||
|
return false
|
||||||
|
if (item.key === 'range' && isHidden('range'))
|
||||||
|
return false
|
||||||
|
if (item.key === 'date' && isHidden('date'))
|
||||||
|
return false
|
||||||
|
if (item.key === 'month' && isHidden('month'))
|
||||||
|
return false
|
||||||
|
if (item.key === 'year' && isHidden('year'))
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const coreModeOptions: { mode: DatePickerMode, label: string }[] = [
|
||||||
|
{ mode: 'date', label: '日' },
|
||||||
|
{ mode: 'month', label: '月' },
|
||||||
|
{ mode: 'year', label: '年' },
|
||||||
|
{ mode: 'time', label: '时间' },
|
||||||
|
{ mode: 'datetime', label: '日期' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const visibleCoreModeOptions = coreModeOptions.filter((item) => {
|
||||||
|
if (item.mode === 'date' && isHidden('date'))
|
||||||
|
return false
|
||||||
|
if (item.mode === 'month' && isHidden('month'))
|
||||||
|
return false
|
||||||
|
if (item.mode === 'year' && isHidden('year'))
|
||||||
|
return false
|
||||||
|
if (item.mode === 'time' && isHidden('time'))
|
||||||
|
return false
|
||||||
|
if (item.mode === 'datetime' && isHidden('datetime'))
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
function isDatePickerMode(
|
||||||
|
mode: DateMorePickerMode,
|
||||||
|
): mode is DatePickerMode {
|
||||||
|
return ['date', 'month', 'year', 'time', 'datetime'].includes(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canConfirm = (() => {
|
||||||
|
if (internalMode !== 'range')
|
||||||
|
return true
|
||||||
|
console.log(internalMode)
|
||||||
|
console.log(internalStart)
|
||||||
|
console.log(internalEnd)
|
||||||
|
|
||||||
|
const startValid = dayjs(internalStart).isValid()
|
||||||
|
const endValid = dayjs(internalEnd).isValid()
|
||||||
|
|
||||||
|
return startValid && endValid
|
||||||
|
})()
|
||||||
|
|
||||||
/** ================= UI ================= */
|
/** ================= UI ================= */
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -200,14 +314,7 @@ export default function DateMorePicker({
|
|||||||
<Text className="text-lg mb-1">快速选择</Text>
|
<Text className="text-lg mb-1">快速选择</Text>
|
||||||
<Tab
|
<Tab
|
||||||
defaultKey={activeTab}
|
defaultKey={activeTab}
|
||||||
data={[
|
data={visibleTabItems}
|
||||||
{ label: '自定义', key: 'custom' },
|
|
||||||
{ label: '不限', key: 'unlimited' },
|
|
||||||
{ label: '今天', key: 'date' },
|
|
||||||
{ label: '本月', key: 'month' },
|
|
||||||
{ label: '本年', key: 'year' },
|
|
||||||
{ label: '范围', key: 'range' },
|
|
||||||
]}
|
|
||||||
onChange={(key) => {
|
onChange={(key) => {
|
||||||
setActiveTab(key as TabKey)
|
setActiveTab(key as TabKey)
|
||||||
handleModeChange(key as PickerModeInternal)
|
handleModeChange(key as PickerModeInternal)
|
||||||
@@ -219,18 +326,22 @@ export default function DateMorePicker({
|
|||||||
{!['unlimited', 'range'].includes(internalMode) && (
|
{!['unlimited', 'range'].includes(internalMode) && (
|
||||||
<View className="mb-2">
|
<View className="mb-2">
|
||||||
<Text className="text-lg mb-1">选择模式</Text>
|
<Text className="text-lg mb-1">选择模式</Text>
|
||||||
|
{visibleCoreModeOptions.length > 0 && (
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
values={coreLabels}
|
|
||||||
selectedIndex={coreModes.indexOf(internalMode as DatePickerMode)}
|
|
||||||
style={{ width: '100%', borderRadius: 2 }}
|
style={{ width: '100%', borderRadius: 2 }}
|
||||||
sliderStyle={{ borderRadius: 2 }}
|
sliderStyle={{ borderRadius: 2 }}
|
||||||
|
values={visibleCoreModeOptions.map(i => i.label)}
|
||||||
|
selectedIndex={visibleCoreModeOptions.findIndex(
|
||||||
|
i => i.mode === internalMode,
|
||||||
|
)}
|
||||||
onChange={(e: any) => {
|
onChange={(e: any) => {
|
||||||
const next = coreModes[e.nativeEvent.selectedSegmentIndex]
|
const next = visibleCoreModeOptions[e.nativeEvent.selectedSegmentIndex].mode
|
||||||
setInternalMode(next)
|
setInternalMode(next)
|
||||||
setInternalStart(dayjs().toDate())
|
setInternalStart(dayjs().toDate())
|
||||||
setActiveTab('custom')
|
setActiveTab('custom')
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -238,14 +349,14 @@ export default function DateMorePicker({
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
|
flexGrow: 1,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingVertical: 10,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{internalMode === 'unlimited' && (
|
{internalMode === 'unlimited' && (
|
||||||
<View className="items-center">
|
<View className="flex-1 w-full h-full items-center justify-center">
|
||||||
<LucideIcon icon="Clock" size={38} color="#555" />
|
<LucideIcon icon="Clock" size={38} color="#555" />
|
||||||
<Text className="mt-2 text-lg">不限时间</Text>
|
<Text className="mt-2 text-lg">不限时间</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -253,6 +364,8 @@ export default function DateMorePicker({
|
|||||||
|
|
||||||
{internalMode === 'range' && (
|
{internalMode === 'range' && (
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
|
startDate={internalStart}
|
||||||
|
endDate={internalEnd}
|
||||||
isModal={false}
|
isModal={false}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
@@ -265,12 +378,13 @@ export default function DateMorePicker({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{coreModes.includes(internalMode as DatePickerMode) && (
|
{isDatePickerMode(internalMode)
|
||||||
|
&& visibleCoreModeOptions.some(i => i.mode === internalMode) && (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
key={internalMode}
|
key={internalMode}
|
||||||
isModal={false}
|
isModal={false}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
mode={internalMode as DatePickerMode}
|
mode={internalMode}
|
||||||
date={toDate(internalStart)}
|
date={toDate(internalStart)}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
onDateChange={(value) => {
|
onDateChange={(value) => {
|
||||||
@@ -286,7 +400,11 @@ export default function DateMorePicker({
|
|||||||
<Button className="flex-1" variant="outline" onPress={handleClose}>
|
<Button className="flex-1" variant="outline" onPress={handleClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="flex-1" onPress={handleConfirm}>
|
<Button
|
||||||
|
className="flex-1"
|
||||||
|
onPress={handleConfirm}
|
||||||
|
disabled={!canConfirm}
|
||||||
|
>
|
||||||
确认
|
确认
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface MoneyRecordStore {
|
|||||||
getTotalExpense: () => string
|
getTotalExpense: () => string
|
||||||
getTotalBalance: () => string
|
getTotalBalance: () => string
|
||||||
|
|
||||||
setRecords: (records: MoneyRecord[], month: string) => void
|
setRecords: (records: MoneyRecord[]) => void
|
||||||
setCurrentMonth: (month: string) => void
|
setCurrentMonth: (month: string) => void
|
||||||
|
|
||||||
getMoneyRecordById: (id: number) => MoneyRecord | null
|
getMoneyRecordById: (id: number) => MoneyRecord | null
|
||||||
@@ -71,12 +71,9 @@ export const useMoneyRecordStore = create<MoneyRecordStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* ---------------- base ---------------- */
|
/* ---------------- base ---------------- */
|
||||||
setRecords: (records, month) => {
|
setRecords: (records) => {
|
||||||
const filtered = records.filter(r =>
|
const newGroups = recomputeGroups(records)
|
||||||
isInMonth(r.record_time, month),
|
set({ records: newGroups })
|
||||||
)
|
|
||||||
const newGroups = recomputeGroups(filtered)
|
|
||||||
set({ records: newGroups, currentMonth: month })
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setCurrentMonth: month => set({ currentMonth: month }),
|
setCurrentMonth: month => set({ currentMonth: month }),
|
||||||
|
|||||||
@@ -119,3 +119,34 @@ export function getStartOfMonth() {
|
|||||||
export function getEndOfMonth() {
|
export function getEndOfMonth() {
|
||||||
return dayjs().endOf('month').format('YYYY-MM-DD')
|
return dayjs().endOf('month').format('YYYY-MM-DD')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeDateTime(
|
||||||
|
input?: string | Date | dayjs.Dayjs,
|
||||||
|
): string {
|
||||||
|
if (!input)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
const raw = typeof input === 'string' ? input.trim() : input
|
||||||
|
let d = dayjs(raw)
|
||||||
|
|
||||||
|
if (!d.isValid())
|
||||||
|
return ''
|
||||||
|
|
||||||
|
// string 才需要“猜精度”,Date / dayjs 默认是完整的
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
if (/^\d{4}$/.test(raw)) {
|
||||||
|
d = d.startOf('year')
|
||||||
|
}
|
||||||
|
else if (/^\d{4}-\d{2}$/.test(raw)) {
|
||||||
|
d = d.startOf('month')
|
||||||
|
}
|
||||||
|
else if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
|
||||||
|
d = d.startOf('day')
|
||||||
|
}
|
||||||
|
else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(raw)) {
|
||||||
|
d = d.second(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
Reference in new issue
Block a user