diff --git a/link_homework/api/v1/record/Record.go b/link_homework/api/v1/record/Record.go index d0d2a62..1c9f0bd 100644 --- a/link_homework/api/v1/record/Record.go +++ b/link_homework/api/v1/record/Record.go @@ -1,6 +1,9 @@ package record -import "link_homework/internal/model/dto" +import ( + "github.com/gogf/gf/v2/os/gtime" + "link_homework/internal/model/dto" +) type GetRecordListReq struct { Id int `json:"id" orm:"" dc:"作业id"` @@ -44,3 +47,22 @@ type GetShopInfoByDeptIdRes struct { ShopId string `json:"shopId" orm:"db:cms;member_info;column:shopId" dc:"门店id"` ShopName string `json:"shopName" orm:"db:cms;member_info;column:shopName" dc:"门店名"` } + +type ExcelExportReq struct { + Jwcode int `json:"jwcode" dc:"精网号"` + Name string `json:"name" dc:"用户名字"` + DeptId string `json:"deptId" dc:"部门id"` + DeptName string `json:"deptName" dc:"部门名"` + ShopId string `json:"shopId" dc:"门店id"` + ShopName string `json:"shopName" dc:"门店名"` + Reply []Reply `json:"Reply" dc:"答案详情"` +} + +type Reply struct { + FormId int `json:"formId"` + Type int `json:"type"` + FormTitle string `json:"formTitle"` + ContentTitle string `json:"contentTitle"` + Content string `json:"content"` + SubmitTime gtime.Time `json:"submitTime"` +} diff --git a/link_homework/go.mod b/link_homework/go.mod index 8321280..cb94b90 100644 --- a/link_homework/go.mod +++ b/link_homework/go.mod @@ -3,6 +3,7 @@ module link_homework go 1.21.13 require ( + github.com/360EntSecGroup-Skylar/excelize v1.4.1 github.com/gogf/gf/contrib/drivers/mysql/v2 v2.8.1 github.com/gogf/gf/contrib/nosql/redis/v2 v2.8.2 github.com/gogf/gf/v2 v2.8.2 @@ -25,6 +26,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/redis/go-redis/v9 v9.7.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/link_homework/go.sum b/link_homework/go.sum index 5ee4499..6cd304b 100644 --- a/link_homework/go.sum +++ b/link_homework/go.sum @@ -1,3 +1,5 @@ +github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= @@ -47,6 +49,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -56,6 +60,7 @@ github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93Ge github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= diff --git a/link_homework/internal/cmd/cmd.go b/link_homework/internal/cmd/cmd.go index d62a5e7..f21252c 100644 --- a/link_homework/internal/cmd/cmd.go +++ b/link_homework/internal/cmd/cmd.go @@ -21,23 +21,6 @@ var ( Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { s := g.Server() //后台 - - ////启动gtoken - //// 创建一个GfToken对象,用于处理用户登录、登出、权限验证等操作 - //gfToken := >oken.GfToken{ - // // 设置登录路径,即用户登录接口登入成功后会获得一个Token - // LoginPath: "/login", - // //// 设置登录前执行的函数,在用户登录之前会调用这个函数进行一些预处理,比如验证用户名和密码等。 - // //LoginBeforeFunc: loginFunc, //手动编写 没有同时配置登入路径,登入方法,登出路径启动时会报错 - // // 设置登出路径,即用户登出接口登入成功后会删除Token - // LogoutPath: "/user/logout", - // //// 设置需要拦截的路径,按照前缀拦截,所有以/user或/system开头的路径都需要进行Token认证。 - // //AuthPaths: g.SliceStr{"/user", "/system"}, - // //// 设置不需要拦截的路径,所有以/user/info或/system/user/开头的路径都不需要进行Token认证。 - // //AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, - // //// 开启全局拦截,默认关闭,如果设置为true,则所有请求都会经过Token认证中间件,如果设置为false,则只有指定路径的请求会经过Token认证中间件。 - // //GlobalMiddleware: true, - //} s.Group("/api/homework_manage", func(group *ghttp.RouterGroup) { group.Middleware(middleware.MiddlewareCORS) //group.Middleware(middleware.MiddlewareIsLogin) @@ -47,6 +30,7 @@ var ( group.POST("/getrecordbycondition", record.NewManageRecord().GetRecordByCondition) group.POST("/getdeptinfo", record.NewManageRecord().GetDeptInfo) group.POST("/getshopinfo", record.NewManageRecord().GetShopInfo) + group.POST("/exceleexport", record.NewManageRecord().ExeclExport) group.POST("/get-homework-list", homework.Homework().GetHomeworkList) group.POST("/get-homework", homework.Homework().GetHomework) group.POST("/add-homework", homework.Homework().AddHomework) diff --git a/link_homework/internal/controller/record/manageRecord.go b/link_homework/internal/controller/record/manageRecord.go index 528512e..1242c75 100644 --- a/link_homework/internal/controller/record/manageRecord.go +++ b/link_homework/internal/controller/record/manageRecord.go @@ -1,10 +1,14 @@ package record import ( + "github.com/360EntSecGroup-Skylar/excelize" "github.com/gogf/gf/v2/net/ghttp" "link_homework/api/v1/record" "link_homework/internal/model/dto" "link_homework/internal/service" + "log" + "net/http" + "strconv" ) type ManageRecord struct{} @@ -96,5 +100,170 @@ func (m *ManageRecord) GetShopInfo(r *ghttp.Request) { Message: "success", Data: result, }) +} + +func (m *ManageRecord) ExeclExport(r *ghttp.Request) { + //获取前端传来的参数 + var req record.GetRecordByConditionReq + if err := r.Parse(&req); err != nil { + r.Response.WriteJsonExit(dto.Result{ + Code: 400, + Message: err.Error(), + }) + } + //获取数据 + data, err := service.Record().GetRecordByCondition(r.Context(), req.Id, req.Jwcode, req.DeptId, req.ShopId, req.PageNo, req.PageSize) + if err != nil { + r.Response.WriteJsonExit(dto.Result{ + Code: 400, + Message: err.Error(), + }) + } + + //创建文件 + f := excelize.NewFile() + sheet := "Sheet1" + f.NewSheet(sheet) + + ////创建表头 + //var header []string + ////for _, field :=range reflect.Type(record.GetRecordListRes{}){ + //// header = append(header, field.Name) + ////} + //for i := 0; i < reflect.TypeOf(record.GetRecordListRes{}).NumField(); i++ { + // header = append(header, reflect.TypeOf(record.GetRecordListRes{}).Field(i).Name) + //} + + headers := []string{"精网号", "用户名字", "部门名", "门店名", "答案详情", "提交时间"} + headerStyle, err := f.NewStyle(`{"font":{"bold":true},"fill":{"type":"solid","color":"#4F81BD"}}`) + if err != nil { + r.Response.WriteJsonExit(dto.Result{ + Code: 400, + Message: err.Error(), + }) + } + for i, header := range headers { + cellName := getExcelColumnName(i) + "1" + f.SetCellValue(sheet, cellName, header) + f.SetCellStyle(sheet, cellName, cellName, headerStyle) + } + + //填充数据 + for rowIndex, row := range data { + // 假设 data 是一个 []GetRecordListRes 类型的切片 + rowData := row //.(record.GetRecordListRes) // 类型断言(这里需要确保 data 的类型是正确的) + // 或者使用反射,但更推荐使用标签和直接映射 + f.SetCellValue(sheet, getExcelColumnName(0)+strconv.Itoa(rowIndex+2), rowData.Jwcode) + f.SetCellValue(sheet, getExcelColumnName(1)+strconv.Itoa(rowIndex+2), rowData.Name) + f.SetCellValue(sheet, getExcelColumnName(2)+strconv.Itoa(rowIndex+2), rowData.DeptName) + f.SetCellValue(sheet, getExcelColumnName(3)+strconv.Itoa(rowIndex+2), rowData.ShopName) + f.SetCellValue(sheet, getExcelColumnName(4)+strconv.Itoa(rowIndex+2), rowData.Reply) + f.SetCellValue(sheet, getExcelColumnName(5)+strconv.Itoa(rowIndex+2), rowData.Reply[0].UpdatedAt.Format("2006-01-02 15:04:05")) + } + + ////定义表头样式,font:定义字体样式,如字体类型、字体大小、字体颜色等,Arial:字体家族为Arial. + ////fill:定义填充样式,如背景色 + //const ( + // headerStyleJSON = `{ + // "font": { + // "bold": true, + // "italic": false, + // "family": "Arial", + // "size": 14, + // "color": "#FFFFFF" //更换成你想要的字体font + // }, + // "fill": { + // "type": "solid", + // "color": "#4F81BD" // 更换为你想要的背景色 + // } + // }` + //) + + ////填充表头 + //headerStyle, err := f.NewStyle(headerStyleJSON) + //if err != nil { + // r.Response.WriteJsonExit(dto.Result{ + // Code: 400, + // Message: err.Error(), + // }) + //} + // + //for i, v := range header { + // cell := fmt.Sprintf("%s%d", getExcelColumnName(i), 1) + // f.SetCellValue(sheet, cell, v) + // f.SetCellStyle(sheet, cell, cell, headerStyle) + //} + // + ////填充数据 + //for i, row := range data { + // for j, col := range header { + // cell := fmt.Sprintf("%s%d", getExcelColumnName(j), i+2) + // //f.SetCellValue(sheet, cell, row[col]) + // f.SetCellValue(sheet, cell, row.FieldByName(col).Interface()) + // } + //} + + //设置默认打开的sheet + f.SetActiveSheet(f.GetSheetIndex(sheet)) + + // 将 Excel 文件作为 HTTP 响应发送给客户端 + r.Response.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + r.Response.Header().Set("Content-Disposition", "attachment; filename=data.xlsx") + r.Response.WriteHeader(http.StatusOK) + if _, err := f.WriteTo(r.Response.Writer); err != nil { //这里有问题,说左右值数量不同,我加了个_ + // 处理错误(注意:这里可能无法再发送 JSON 响应,因为响应头已经发送) + log.Printf("Error writing Excel file to response: %v", err) + } + + ////保存文件 + //if err := f.SaveAs("data.xlsx"); err != nil { + // r.Response.WriteJsonExit(dto.Result{ + // Code: 400, + // Message: err.Error(), + // }) + //} + // + //r.Response.WriteJsonExit(dto.Result{ + // Code: 200, + // Message: "success", + // Data: "data.xlsx", + //}) + +} + +//func getExcelColumnName(index int) string { +// var result string +// for index > 0 { +// index-- +// result = string('A'+index%26) + result +// index /= 26 +// } +// return result +//} + +// getExcelColumnName 将整数索引转换为 Excel 列名 +func getExcelColumnName(index int) string { + if index <= 0 { + return "" + } + + const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + var columnName string + + for index > 0 { + // 取余数得到当前位的字母 + remainder := index % 26 + // 将字母添加到结果字符串的前面 + columnName = string(letters[remainder-1]) + columnName + // 去掉已经处理过的最低位(因为 Excel 列名是从 1 开始计数的,所以这里要减 1) + // 但由于我们是从后往前构建字符串,所以实际上是在下一次循环中处理 index/26 的结果 + index = (index - remainder) / 26 + // 由于上面的计算中 index-remainder 可能是 26 的倍数(当 remainder 为 0 时),这样会导致 index 多减 1, + // 所以当 remainder 为 0 时,我们需要将 index 加回 1(或者等价地,不执行 index-- 操作)。 + // 但由于我们在循环中每次都会执行 index = (index - remainder) / 26,并且当 remainder 为 0 时, + // (index - 0) / 26 实际上就是 index / 26,这是正确的。因此,我们不需要在这里对 remainder 为 0 的情况进行特殊处理。 + // 这里的注释主要是为了解释为什么上面的计算是正确的,以及为什么不需要对 remainder 为 0 的情况进行额外处理。 + } + return columnName } diff --git a/link_homework/internal/logic/record/record.go b/link_homework/internal/logic/record/record.go index 0900281..3eea23d 100644 --- a/link_homework/internal/logic/record/record.go +++ b/link_homework/internal/logic/record/record.go @@ -24,8 +24,12 @@ func NewRecord() *sRecord { // 无条件全查 func (s *sRecord) GetRecordList(ctx context.Context, groupId, pageNo, pageSize int) (record []pkgRecord.GetRecordListRes, err error) { //从record表中查询出jwcode,根据group_id - err = dao.ActivityInteractiveRecord.Ctx(ctx).Fields("jwcode").Where("group_id", groupId).Group("jwcode"). - Page(pageNo, pageSize).Scan(&record) + if pageNo == 0 && pageSize == 0 { + err = dao.ActivityInteractiveRecord.Ctx(ctx).Fields("jwcode").Where("group_id", groupId).Group("jwcode").Scan(&record) + } else { + err = dao.ActivityInteractiveRecord.Ctx(ctx).Fields("jwcode").Where("group_id", groupId).Group("jwcode"). + Page(pageNo, pageSize).Scan(&record) + } if err != nil { return nil, errors.New("无条件查jwcode失败") } @@ -46,7 +50,7 @@ func (s *sRecord) GetRecordList(ctx context.Context, groupId, pageNo, pageSize i //根据jwcode,groupId在record表中查询最新的提交记录进行存放 var recordInfo []dto.RecordInfo err = dao.ActivityInteractiveRecord.Ctx(ctx).Fields("content", "content_title", "updated_at", "form_id"). - Where("jwcode", info.Jwcode).Where("group_id", groupId).Group("form_id").Order("updated_at desc").Scan(&recordInfo) //不是最新的,需要再改 + Where("jwcode", info.Jwcode).Where("group_id", groupId).Group("form_id").Order("updated_at desc").Scan(&recordInfo) if err != nil { return nil, err } @@ -62,7 +66,7 @@ func (s *sRecord) GetRecordList(ctx context.Context, groupId, pageNo, pageSize i return } -// 根据条件查询 +// 根据条件查询 对所有结果进行筛选 func (s *sRecord) GetRecordByCondition(ctx context.Context, groupId, jwcode int, deptId, shopId string, pageNo, pageSize int) (record []pkgRecord.GetRecordListRes, err error) { //全查 recordList, err := s.GetRecordList(ctx, groupId, pageNo, pageSize) diff --git a/link_homework/internal/service/record.go b/link_homework/internal/service/record.go index ba08449..8e87bac 100644 --- a/link_homework/internal/service/record.go +++ b/link_homework/internal/service/record.go @@ -14,7 +14,7 @@ type ( IRecord interface { // 无条件全查 GetRecordList(ctx context.Context, groupId int, pageNo int, pageSize int) (record []pkgRecord.GetRecordListRes, err error) - // 根据条件查询 + // 根据条件查询 对所有结果进行筛选 GetRecordByCondition(ctx context.Context, groupId int, jwcode int, deptId string, shopId string, pageNo int, pageSize int) (record []pkgRecord.GetRecordListRes, err error) // 查询部门信息 GetDeptInfo(ctx context.Context) (depts []pkgRecord.GetDeptInfoRes, err error)