This commit is contained in:
戴育兵 2025-12-17 15:12:55 +08:00
commit 9731c02a63
7 changed files with 2659 additions and 2143 deletions

File diff suppressed because it is too large Load Diff

View File

@ -483,6 +483,22 @@ func (this *ConfirmWorkReq) Validate() error {
func (this *ConfirmWorkResp) Validate() error { func (this *ConfirmWorkResp) Validate() error {
return nil return nil
} }
func (this *ConfirmWorkItem) Validate() error {
return nil
}
func (this *GetWaitConfirmWorkListReq) Validate() error {
return nil
}
func (this *GetWaitConfirmWorkListResp) Validate() error {
for _, item := range this.Data {
if item != nil {
if err := github_com_mwitkow_go_proto_validators.CallValidatorIfExists(item); err != nil {
return github_com_mwitkow_go_proto_validators.FieldError("Data", err)
}
}
}
return nil
}
func (this *AutoCreateUserAndOrderRequest) Validate() error { func (this *AutoCreateUserAndOrderRequest) Validate() error {
return nil return nil
} }

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-triple. DO NOT EDIT. // Code generated by protoc-gen-go-triple. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-triple v1.0.8 // - protoc-gen-go-triple v1.0.5
// - protoc v3.21.1 // - protoc v5.26.0
// source: pb/bundle.proto // source: pb/bundle.proto
package bundle package bundle
@ -79,6 +79,7 @@ type BundleClient interface {
GetVedioWorkDetail(ctx context.Context, in *GetVedioWorkDetailReq, opts ...grpc_go.CallOption) (*GetVedioeWorkDetailResp, common.ErrorWithAttachment) GetVedioWorkDetail(ctx context.Context, in *GetVedioWorkDetailReq, opts ...grpc_go.CallOption) (*GetVedioeWorkDetailResp, common.ErrorWithAttachment)
ToBeComfirmedWorks(ctx context.Context, in *ToBeComfirmedWorksReq, opts ...grpc_go.CallOption) (*ToBeComfirmedWorksResp, common.ErrorWithAttachment) ToBeComfirmedWorks(ctx context.Context, in *ToBeComfirmedWorksReq, opts ...grpc_go.CallOption) (*ToBeComfirmedWorksResp, common.ErrorWithAttachment)
ConfirmWork(ctx context.Context, in *ConfirmWorkReq, opts ...grpc_go.CallOption) (*ConfirmWorkResp, common.ErrorWithAttachment) ConfirmWork(ctx context.Context, in *ConfirmWorkReq, opts ...grpc_go.CallOption) (*ConfirmWorkResp, common.ErrorWithAttachment)
GetWaitConfirmWorkList(ctx context.Context, in *GetWaitConfirmWorkListReq, opts ...grpc_go.CallOption) (*GetWaitConfirmWorkListResp, common.ErrorWithAttachment)
// 对账单 // 对账单
GetReconciliationList(ctx context.Context, in *GetReconciliationListReq, opts ...grpc_go.CallOption) (*GetReconciliationListResp, common.ErrorWithAttachment) GetReconciliationList(ctx context.Context, in *GetReconciliationListReq, opts ...grpc_go.CallOption) (*GetReconciliationListResp, common.ErrorWithAttachment)
CreateReconciliation(ctx context.Context, in *ReconciliationInfo, opts ...grpc_go.CallOption) (*CommonResponse, common.ErrorWithAttachment) CreateReconciliation(ctx context.Context, in *ReconciliationInfo, opts ...grpc_go.CallOption) (*CommonResponse, common.ErrorWithAttachment)
@ -169,6 +170,7 @@ type BundleClientImpl struct {
GetVedioWorkDetail func(ctx context.Context, in *GetVedioWorkDetailReq) (*GetVedioeWorkDetailResp, error) GetVedioWorkDetail func(ctx context.Context, in *GetVedioWorkDetailReq) (*GetVedioeWorkDetailResp, error)
ToBeComfirmedWorks func(ctx context.Context, in *ToBeComfirmedWorksReq) (*ToBeComfirmedWorksResp, error) ToBeComfirmedWorks func(ctx context.Context, in *ToBeComfirmedWorksReq) (*ToBeComfirmedWorksResp, error)
ConfirmWork func(ctx context.Context, in *ConfirmWorkReq) (*ConfirmWorkResp, error) ConfirmWork func(ctx context.Context, in *ConfirmWorkReq) (*ConfirmWorkResp, error)
GetWaitConfirmWorkList func(ctx context.Context, in *GetWaitConfirmWorkListReq) (*GetWaitConfirmWorkListResp, error)
GetReconciliationList func(ctx context.Context, in *GetReconciliationListReq) (*GetReconciliationListResp, error) GetReconciliationList func(ctx context.Context, in *GetReconciliationListReq) (*GetReconciliationListResp, error)
CreateReconciliation func(ctx context.Context, in *ReconciliationInfo) (*CommonResponse, error) CreateReconciliation func(ctx context.Context, in *ReconciliationInfo) (*CommonResponse, error)
UpdateReconciliation func(ctx context.Context, in *ReconciliationInfo) (*CommonResponse, error) UpdateReconciliation func(ctx context.Context, in *ReconciliationInfo) (*CommonResponse, error)
@ -497,6 +499,12 @@ func (c *bundleClient) ConfirmWork(ctx context.Context, in *ConfirmWorkReq, opts
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/ConfirmWork", in, out) return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/ConfirmWork", in, out)
} }
func (c *bundleClient) GetWaitConfirmWorkList(ctx context.Context, in *GetWaitConfirmWorkListReq, opts ...grpc_go.CallOption) (*GetWaitConfirmWorkListResp, common.ErrorWithAttachment) {
out := new(GetWaitConfirmWorkListResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string)
return out, c.cc.Invoke(ctx, "/"+interfaceKey+"/GetWaitConfirmWorkList", in, out)
}
func (c *bundleClient) GetReconciliationList(ctx context.Context, in *GetReconciliationListReq, opts ...grpc_go.CallOption) (*GetReconciliationListResp, common.ErrorWithAttachment) { func (c *bundleClient) GetReconciliationList(ctx context.Context, in *GetReconciliationListReq, opts ...grpc_go.CallOption) (*GetReconciliationListResp, common.ErrorWithAttachment) {
out := new(GetReconciliationListResp) out := new(GetReconciliationListResp)
interfaceKey := ctx.Value(constant.InterfaceKey).(string) interfaceKey := ctx.Value(constant.InterfaceKey).(string)
@ -744,6 +752,7 @@ type BundleServer interface {
GetVedioWorkDetail(context.Context, *GetVedioWorkDetailReq) (*GetVedioeWorkDetailResp, error) GetVedioWorkDetail(context.Context, *GetVedioWorkDetailReq) (*GetVedioeWorkDetailResp, error)
ToBeComfirmedWorks(context.Context, *ToBeComfirmedWorksReq) (*ToBeComfirmedWorksResp, error) ToBeComfirmedWorks(context.Context, *ToBeComfirmedWorksReq) (*ToBeComfirmedWorksResp, error)
ConfirmWork(context.Context, *ConfirmWorkReq) (*ConfirmWorkResp, error) ConfirmWork(context.Context, *ConfirmWorkReq) (*ConfirmWorkResp, error)
GetWaitConfirmWorkList(context.Context, *GetWaitConfirmWorkListReq) (*GetWaitConfirmWorkListResp, error)
// 对账单 // 对账单
GetReconciliationList(context.Context, *GetReconciliationListReq) (*GetReconciliationListResp, error) GetReconciliationList(context.Context, *GetReconciliationListReq) (*GetReconciliationListResp, error)
CreateReconciliation(context.Context, *ReconciliationInfo) (*CommonResponse, error) CreateReconciliation(context.Context, *ReconciliationInfo) (*CommonResponse, error)
@ -929,6 +938,9 @@ func (UnimplementedBundleServer) ToBeComfirmedWorks(context.Context, *ToBeComfir
func (UnimplementedBundleServer) ConfirmWork(context.Context, *ConfirmWorkReq) (*ConfirmWorkResp, error) { func (UnimplementedBundleServer) ConfirmWork(context.Context, *ConfirmWorkReq) (*ConfirmWorkResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmWork not implemented") return nil, status.Errorf(codes.Unimplemented, "method ConfirmWork not implemented")
} }
func (UnimplementedBundleServer) GetWaitConfirmWorkList(context.Context, *GetWaitConfirmWorkListReq) (*GetWaitConfirmWorkListResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWaitConfirmWorkList not implemented")
}
func (UnimplementedBundleServer) GetReconciliationList(context.Context, *GetReconciliationListReq) (*GetReconciliationListResp, error) { func (UnimplementedBundleServer) GetReconciliationList(context.Context, *GetReconciliationListReq) (*GetReconciliationListResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetReconciliationList not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetReconciliationList not implemented")
} }
@ -2416,6 +2428,35 @@ func _Bundle_ConfirmWork_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _Bundle_GetWaitConfirmWorkList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(GetWaitConfirmWorkListReq)
if err := dec(in); err != nil {
return nil, err
}
base := srv.(dubbo3.Dubbo3GrpcService)
args := []interface{}{}
args = append(args, in)
md, _ := metadata.FromIncomingContext(ctx)
invAttachment := make(map[string]interface{}, len(md))
for k, v := range md {
invAttachment[k] = v
}
invo := invocation.NewRPCInvocation("GetWaitConfirmWorkList", args, invAttachment)
if interceptor == nil {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
info := &grpc_go.UnaryServerInfo{
Server: srv,
FullMethod: ctx.Value("XXX_TRIPLE_GO_INTERFACE_NAME").(string),
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
result := base.XXX_GetProxyImpl().Invoke(ctx, invo)
return result, result.Error()
}
return interceptor(ctx, in, info, handler)
}
func _Bundle_GetReconciliationList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) { func _Bundle_GetReconciliationList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc_go.UnaryServerInterceptor) (interface{}, error) {
in := new(GetReconciliationListReq) in := new(GetReconciliationListReq)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -3539,6 +3580,10 @@ var Bundle_ServiceDesc = grpc_go.ServiceDesc{
MethodName: "ConfirmWork", MethodName: "ConfirmWork",
Handler: _Bundle_ConfirmWork_Handler, Handler: _Bundle_ConfirmWork_Handler,
}, },
{
MethodName: "GetWaitConfirmWorkList",
Handler: _Bundle_GetWaitConfirmWorkList_Handler,
},
{ {
MethodName: "GetReconciliationList", MethodName: "GetReconciliationList",
Handler: _Bundle_GetReconciliationList_Handler, Handler: _Bundle_GetReconciliationList_Handler,

View File

@ -5,12 +5,16 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast" "fonchain-fiee/api/cast"
"fonchain-fiee/pkg/cache" "fonchain-fiee/pkg/cache"
bundleModel "fonchain-fiee/pkg/model/bundle"
modelCast "fonchain-fiee/pkg/model/cast" modelCast "fonchain-fiee/pkg/model/cast"
"fonchain-fiee/pkg/service" "fonchain-fiee/pkg/service"
serverCast "fonchain-fiee/pkg/service/cast" serverCast "fonchain-fiee/pkg/service/cast"
"log" "log"
"math/rand"
"strconv"
"time" "time"
"github.com/go-redis/redis" "github.com/go-redis/redis"
@ -32,6 +36,9 @@ func InitTasks() error {
// 启动队列消费者 // 启动队列消费者
go WorkPublishQueueConsumer() go WorkPublishQueueConsumer()
// 启动随机间隔的自动确认任务
go AutoManuallyConfirmWorkTaskWithRandomInterval()
return nil return nil
} }
@ -51,6 +58,127 @@ func RefreshWorkApprovalStatusTask() {
serverCast.RefreshWorkApproval(nil, resp.Data) serverCast.RefreshWorkApproval(nil, resp.Data)
} }
// AutoManuallyConfirmWorkTaskWithRandomInterval 以随机间隔3-7分钟执行自动确认任务
func AutoManuallyConfirmWorkTaskWithRandomInterval() {
for {
// 执行任务
AutoManuallyConfirmWorkTask()
// 生成3-7分钟之间的随机间隔单位分钟
randomMinutes := rand.Intn(5) + 3 // 3-7分钟
randomDuration := time.Duration(randomMinutes) * time.Minute
// 等待随机时间
time.Sleep(randomDuration)
}
}
func AutoManuallyConfirmWorkTask() {
var req bundle.GetWaitConfirmWorkListReq
res, err := service.BundleProvider.GetWaitConfirmWorkList(context.Background(), &req)
if err != nil {
zap.L().Error("获取待确认作品列表失败", zap.Error(err))
return
}
if res.Data == nil || len(res.Data) == 0 {
return
}
for _, work := range res.Data {
var req bundleModel.UserWorkConfirmReq
req.WorkUuid = work.WorkUuid
req.ConfirmStatus = 1
artistId, err := strconv.ParseInt(work.ArtistUuid, 10, 64)
if err != nil {
zap.L().Error("解析艺术家ID失败", zap.Error(err))
return
}
if req.ConfirmStatus == 2 { // 驳回完直接结束
_, err := service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 2,
})
if err != nil {
zap.L().Error("确认作品失败", zap.Error(err))
return
}
continue
}
balanceInfoRes, err := service.BundleProvider.GetBundleBalanceByUserId(context.Background(), &bundle.GetBundleBalanceByUserIdReq{
UserId: int32(artistId),
})
if err != nil {
continue
}
wordInfoRes, err := service.CastProvider.WorkDetail(context.Background(), &cast.WorkDetailReq{
WorkUuid: req.WorkUuid,
})
if err != nil {
continue
}
if wordInfoRes.WorkStatus != 4 {
continue
}
var workCategory = wordInfoRes.WorkCategory
var addBalanceReq bundle.AddBundleBalanceReq
addBalanceReq.UserId = int32(artistId)
log.Printf("开始确认作品uuid:" + req.WorkUuid)
switch workCategory {
case 1:
{
if balanceInfoRes.ImageConsumptionNumber >= balanceInfoRes.ImageExtendNumber { // 图文余量不足
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
})
log.Printf("图文余量不足,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
}
addBalanceReq.ImageConsumptionNumber = 1
}
case 2:
{
if balanceInfoRes.VideoConsumptionNumber >= balanceInfoRes.VideoExtendNumber { // 视频余量不足
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
ConfirmStatus: 3,
})
log.Printf("视频余量不足,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
}
addBalanceReq.VideoConsumptionNumber = 1
}
default:
continue
}
resp, err := service.BundleProvider.AddBundleBalance(context.Background(), &addBalanceReq)
if err != nil {
log.Printf("扣除余额失败,作品uuid:"+req.WorkUuid, zap.Error(err))
continue
}
log.Printf("扣除余额成功,作品uuid:" + req.WorkUuid)
_, err = service.CastProvider.UpdateStatus(context.Background(), &cast.UpdateStatusReq{
WorkAction: cast.WorkActionENUM_CONFIRM,
WorkUuid: req.WorkUuid,
ConfirmRemark: req.ConfirmRemark,
CostType: resp.UsedType,
ConfirmStatus: 1,
})
if err != nil {
log.Printf("更新作品状态失败,作品uuid:"+req.WorkUuid, zap.Error(err))
return
}
}
}
func ArtistAutoConfirmTask() { func ArtistAutoConfirmTask() {
now := float64(time.Now().Unix()) now := float64(time.Now().Unix())
opt := redis.ZRangeBy{ opt := redis.ZRangeBy{

View File

@ -155,6 +155,11 @@ const (
ErrorBalanceInsufficient = "余额不足" ErrorBalanceInsufficient = "余额不足"
) )
const (
BundlePurchaseExport = 1
BundleDetailExport = 2
)
// GetMsg 获取状态码对应信息 // GetMsg 获取状态码对应信息
func GetMsg(code int) string { func GetMsg(code int) string {
msg, ok := MsgFlags[code] msg, ok := MsgFlags[code]

View File

@ -208,7 +208,7 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
switch workCategory { switch workCategory {
case 1: case 1:
{ {
if balanceInfoRes.ImageConsumptionNumber >= balanceInfoRes.ImageNumber { // 图文余量不足 if balanceInfoRes.ImageConsumptionNumber >= balanceInfoRes.ImageExtendNumber { // 图文余量不足
service.Error(c, errors.New("图文余量不足")) service.Error(c, errors.New("图文余量不足"))
return return
} }
@ -216,7 +216,7 @@ func WorkConfirm(c *gin.Context) { // 确认作品并扣除余量
} }
case 2: case 2:
{ {
if balanceInfoRes.VideoConsumptionNumber >= balanceInfoRes.VideoNumber { // 视频余量不足 if balanceInfoRes.VideoConsumptionNumber >= balanceInfoRes.VideoExtendNumber { // 视频余量不足
service.Error(c, errors.New("视频余量不足")) service.Error(c, errors.New("视频余量不足"))
return return
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"fonchain-fiee/api/bundle" "fonchain-fiee/api/bundle"
"fonchain-fiee/api/cast" "fonchain-fiee/api/cast"
"fonchain-fiee/pkg/e"
logicCast "fonchain-fiee/pkg/logic/cast" logicCast "fonchain-fiee/pkg/logic/cast"
"fonchain-fiee/pkg/model/login" "fonchain-fiee/pkg/model/login"
"fonchain-fiee/pkg/service" "fonchain-fiee/pkg/service"
@ -97,13 +98,13 @@ func MetricsBundlePurchaseExport(ctx *gin.Context) {
sumFee = sumFee.Add(decimal.NewFromFloat(float64(i.FeeAmount))) sumFee = sumFee.Add(decimal.NewFromFloat(float64(i.FeeAmount)))
} }
f.SetCellValue(sheet, fmt.Sprintf("A%d", endRow), "合计支付金额(美元)") f.SetCellValue(sheet, fmt.Sprintf("A%d", endRow+1), "合计支付金额(美元)")
f.SetCellValue(sheet, fmt.Sprintf("B%d", endRow), "合计结算金额(美元)") f.SetCellValue(sheet, fmt.Sprintf("B%d", endRow+1), "合计结算金额(美元)")
f.SetCellValue(sheet, fmt.Sprintf("C%d", endRow), "合计手续费金额(美元)") f.SetCellValue(sheet, fmt.Sprintf("C%d", endRow+1), "合计手续费金额(美元)")
f.SetCellValue(sheet, fmt.Sprintf("A%d", endRow+1), "$"+sumPayment.StringFixed(2)) f.SetCellValue(sheet, fmt.Sprintf("A%d", endRow+2), "$"+sumPayment.StringFixed(2))
f.SetCellValue(sheet, fmt.Sprintf("B%d", endRow+1), "$"+sumFinal.StringFixed(2)) f.SetCellValue(sheet, fmt.Sprintf("B%d", endRow+2), "$"+sumFinal.StringFixed(2))
f.SetCellValue(sheet, fmt.Sprintf("C%d", endRow+1), "$"+sumFee.StringFixed(2)) f.SetCellValue(sheet, fmt.Sprintf("C%d", endRow+2), "$"+sumFee.StringFixed(2))
// 创建黑色边框样式 // 创建黑色边框样式
borderStyle, err := f.NewStyle(&excelize.Style{ borderStyle, err := f.NewStyle(&excelize.Style{
@ -124,8 +125,8 @@ func MetricsBundlePurchaseExport(ctx *gin.Context) {
} }
// 应用样式到合计区域(包括标题行和数值行) // 应用样式到合计区域(包括标题行和数值行)
startCell := fmt.Sprintf("A%d", endRow) startCell := fmt.Sprintf("A%d", endRow+1)
endCell := fmt.Sprintf("C%d", endRow+1) endCell := fmt.Sprintf("C%d", endRow+2)
if err := f.SetCellStyle(sheet, startCell, endCell, borderStyle); err != nil { if err := f.SetCellStyle(sheet, startCell, endCell, borderStyle); err != nil {
fmt.Println("设置边框样式失败:", err) fmt.Println("设置边框样式失败:", err)
} }
@ -133,8 +134,8 @@ func MetricsBundlePurchaseExport(ctx *gin.Context) {
} }
if err := exportStructToExcel(resp.Data, []string{ if err := exportStructToExcel(resp.Data, []string{
"订单编号", "套餐", "用户编号", "客户姓名", "手机号", "支付时间", "套餐视频数", "增值视频数", "套餐金额", "增值金额", "支付金额", "结算金额", "手续费", "汇率(%", "订单编号", "套餐", "用户编号", "客户姓名", "手机号", "支付时间", "增值视频数", "套餐金额", "增值金额", "支付金额", "结算金额", "手续费", "汇率(%",
}, filePath, statistic); err != nil { }, filePath, e.BundlePurchaseExport, req.EndTime, statistic); err != nil {
service.Error(ctx, errors.New(common.MetricsBundlePurchaseExportFailed)) service.Error(ctx, errors.New(common.MetricsBundlePurchaseExportFailed))
return return
} }
@ -441,7 +442,7 @@ func MetricsBalanceDetailExport(ctx *gin.Context) {
"所属月份", "用户编号", "姓名", "手机号", "购买套餐时间", "套餐金额", "增值金额", "支付金额", "币种", "手续费", "套餐视频总数", "所属月份", "用户编号", "姓名", "手机号", "购买套餐时间", "套餐金额", "增值金额", "支付金额", "币种", "手续费", "套餐视频总数",
"增值视频总数", "套餐视频单价", "增值视频单价", "当前需要上传套餐视频数", "当前需要上传增值视频数", "增值视频总数", "套餐视频单价", "增值视频单价", "当前需要上传套餐视频数", "当前需要上传增值视频数",
"当前已上传套餐视频数", "当前已上传增值视频数", "当前套餐视频已消费总金额", "当前增值视频已消费总金额", "当前已上传套餐视频数", "当前已上传增值视频数", "当前套餐视频已消费总金额", "当前增值视频已消费总金额",
}, filePath, yelloStyle, statistic); err != nil { }, filePath, e.BundleDetailExport, "", statistic); err != nil {
service.Error(ctx, errors.New(common.MetricsBalanceDetailExportFailed)) service.Error(ctx, errors.New(common.MetricsBalanceDetailExportFailed))
return return
} }
@ -518,11 +519,28 @@ func BalanceMetricsExport(ctx *gin.Context) {
}) })
} }
func exportStructToExcel[T any](data []T, headers []string, filename string, fns ...func(data []T, headers []string, f *excelize.File)) error { func exportStructToExcel[T any](data []T, headers []string, filename string, exportType int, endTime string, fns ...func(data []T, headers []string, f *excelize.File)) error {
f := excelize.NewFile() f := excelize.NewFile()
sheet := f.GetSheetName(f.GetActiveSheetIndex()) sheet := f.GetSheetName(f.GetActiveSheetIndex())
// 创建黄色背景样式(用于截止行)
yellowStyle, err := f.NewStyle(&excelize.Style{
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#FFFF00"}, // 黄色
Pattern: 1, // 实心填充
},
Alignment: &excelize.Alignment{
Horizontal: "left",
Vertical: "center",
},
})
if err != nil {
// 如果创建样式失败,继续执行但不应用样式
yellowStyle = 0
}
// 写入表头 // 写入表头
for i, h := range headers { for i, h := range headers {
cell, _ := excelize.CoordinatesToCellName(i+1, 1) cell, _ := excelize.CoordinatesToCellName(i+1, 1)
@ -546,12 +564,52 @@ func exportStructToExcel[T any](data []T, headers []string, filename string, fns
} }
// 写入数据 // 写入数据
for rowIdx, item := range data { actualRowIdx := 0
flag := 0
for _, item := range data {
val := reflect.ValueOf(item) val := reflect.ValueOf(item)
if val.Kind() == reflect.Ptr { if val.Kind() == reflect.Ptr {
val = val.Elem() val = val.Elem()
} }
if exportType == e.BundlePurchaseExport {
if endTime != "" {
// 通过反射获取 PayTime 字段
payTimeField := val.FieldByName("PayTime")
if payTimeField.IsValid() && payTimeField.Kind() == reflect.String {
payTimeStr := payTimeField.String()
if payTimeStr != "" {
// 解析时间
endTimeParsed, err1 := time.Parse(time.DateTime, endTime)
payTimeParsed, err2 := time.Parse(time.DateTime, payTimeStr)
if err1 == nil && err2 == nil {
// 如果 endTime <= PayTime需要插入截止行
if !endTimeParsed.After(payTimeParsed) && flag == 0 {
flag = 1
// 格式化截止时间显示
endTimeFormatted := endTimeParsed.Format("2006年01月02日15点04分")
// 在当前行写入"截止xxxx年xx月xx点"
cell, _ := excelize.CoordinatesToCellName(1, actualRowIdx+2)
f.SetCellValue(sheet, cell, fmt.Sprintf("截止%s", endTimeFormatted))
// 应用黄色背景样式
if yellowStyle > 0 {
// 合并整行的单元格以显示截止信息
lastCol, _ := excelize.ColumnNumberToName(len(headers))
startCell := cell
endCell := fmt.Sprintf("%s%d", lastCol, actualRowIdx+2)
f.MergeCell(sheet, startCell, endCell)
f.SetCellStyle(sheet, startCell, endCell, yellowStyle)
}
// 移动到下一行
actualRowIdx++
}
}
}
}
}
}
for colIdx, fieldIdx := range exportedFields { for colIdx, fieldIdx := range exportedFields {
field := val.Field(fieldIdx) field := val.Field(fieldIdx)
@ -563,9 +621,10 @@ func exportStructToExcel[T any](data []T, headers []string, filename string, fns
cellValue = field.Interface() cellValue = field.Interface()
} }
cell, _ := excelize.CoordinatesToCellName(colIdx+1, rowIdx+2) cell, _ := excelize.CoordinatesToCellName(colIdx+1, actualRowIdx+2)
f.SetCellValue(sheet, cell, cellValue) f.SetCellValue(sheet, cell, cellValue)
} }
actualRowIdx++
} }
} }