feat: excel export (only primitive types)

This commit is contained in:
2025-01-08 22:52:06 +07:00
parent 301c50ebac
commit 8552e651ab
5 changed files with 204 additions and 6 deletions

View File

@@ -36,6 +36,11 @@ export function Delete(item: $models.Post): Promise<$models.Post> & { cancel():
return $typingPromise;
}
export function ExportToExcel(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(75322242) as any;
return $resultPromise;
}
export function GetAll(): Promise<($models.Post | null)[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(65691059) as any;
let $typingPromise = $resultPromise.then(($result) => {

View File

@@ -16,6 +16,7 @@ const doGreet = () => {
onMounted(async () => {
console.log(await PostService.GetById(5))
await PostService.ExportToExcel()
})
</script>

View File

@@ -1,18 +1,33 @@
package excel
import (
"app/internal/services"
"app/internal/dialogs"
"fmt"
"github.com/xuri/excelize/v2"
"log/slog"
"reflect"
"slices"
)
type TableHeaders struct {
Headers []string
IgnoredFieldsIndices []uint
IgnoredFieldsIndices []int
}
func ExportEntityToSpreadsheet[T any](filename, sheetName string, entity T, service services.Service[T]) error {
func isPrimitive(valueType reflect.Type) bool {
switch valueType.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64,
reflect.String:
return true
default:
return false
}
}
func ExportEntityToSpreadsheet[T any](filename, sheetName string, entity T, provider func() ([]*T, error)) error {
file := excelize.NewFile()
defer func() {
if err := file.Close(); err != nil {
@@ -20,9 +35,86 @@ func ExportEntityToSpreadsheet[T any](filename, sheetName string, entity T, serv
}
}()
if _, err := file.NewSheet(sheetName); err != nil {
return err
}
if err := file.DeleteSheet("Sheet1"); err != nil {
return err
}
headers, err := WriteHeaders(sheetName, entity, file)
if err != nil {
return err
}
items, err := provider()
if err != nil {
return err
}
// TODO: process composite objects
// TODO: appearance
for i, item := range items {
structValue := reflect.ValueOf(item).Elem()
for j := range structValue.NumField() {
if slices.Contains(headers.IgnoredFieldsIndices, j) {
continue
}
field := structValue.Field(j)
if isPrimitive(field.Type()) {
fieldValue := reflect.ValueOf(field)
cellName, err := GetCellNameByIndices(j, i+1)
if err != nil {
return err
}
fmt.Printf("Field %s value: %v\n", cellName, fieldValue.Interface())
err = file.SetCellValue(sheetName, cellName, fieldValue.Interface())
if err != nil {
return err
}
}
}
}
filepath, err := dialogs.SaveFileDialog("Экспорт данных", filename)
if err != nil {
return err
}
if err := file.SaveAs(filepath); err != nil {
return err
}
return nil
}
func GetHeaderCellNameByIndex(column int) (string, error) {
colName, err := excelize.ColumnNumberToName(column + 1)
if err != nil {
return "", err
}
cellName, err := excelize.JoinCellName(colName, 1)
if err != nil {
return "", err
}
return cellName, nil
}
func GetCellNameByIndices(column int, row int) (string, error) {
colName, err := excelize.ColumnNumberToName(column + 1)
if err != nil {
return "", err
}
cellName, err := excelize.JoinCellName(colName, row+1)
if err != nil {
return "", err
}
return cellName, nil
}
func ExportHeaders(entity any) TableHeaders {
headers := TableHeaders{}
v := reflect.TypeOf(entity)
@@ -30,8 +122,98 @@ func ExportHeaders(entity any) TableHeaders {
field := v.Field(i)
displayName := field.Tag.Get("displayName")
if displayName != "" {
headers.Headers = append(headers.Headers, displayName)
} else {
headers.IgnoredFieldsIndices = append(headers.IgnoredFieldsIndices, i)
}
}
return headers
}
func WriteHeaders(sheetName string, entity any, file *excelize.File) (TableHeaders, error) {
headers := ExportHeaders(entity)
for i, header := range headers.Headers {
cellName, err := GetHeaderCellNameByIndex(i)
if err != nil {
return headers, err
}
err = file.SetCellValue(sheetName, cellName, header)
if err != nil {
return headers, err
}
}
err := ApplyStyleHeaders(file, sheetName, headers)
if err != nil {
return headers, err
}
return headers, nil
}
func GetStyleId(f *excelize.File, style *excelize.Style) (int, error) {
styleId, err := f.NewStyle(style)
if err != nil {
return 0, fmt.Errorf("ошибка при создании стиля: %w", err)
}
return styleId, nil
}
func LoadHeadersStyle(file *excelize.File) (int, error) {
headersStyle := excelize.Style{
Alignment: &excelize.Alignment{
Horizontal: "center",
Vertical: "center",
},
Border: []excelize.Border{
{
Type: "left",
Color: "000000",
Style: 1,
},
{
Type: "right",
Color: "000000",
Style: 1,
},
{
Type: "top",
Color: "000000",
Style: 1,
},
{
Type: "bottom",
Color: "000000",
Style: 1,
},
},
Font: &excelize.Font{
Bold: true,
VertAlign: "center",
},
}
return GetStyleId(file, &headersStyle)
}
func ApplyStyleHeaders(file *excelize.File, sheetName string, headers TableHeaders) error {
styleId, err := LoadHeadersStyle(file)
if err != nil {
return err
}
cellName, err := GetHeaderCellNameByIndex(len(headers.Headers) - 1)
if err != nil {
return err
}
err = file.SetCellStyle(sheetName, "A1", cellName, styleId)
if err != nil {
return err
}
return nil
}
func WriteData(file *excelize.File) {
}

View File

@@ -5,8 +5,8 @@ var Entities = []any{
}
type Post struct {
Id uint `gorm:"primaryKey"`
Text string `displayName:"Текст"`
Id uint `gorm:"primaryKey" displayName:"Номер"`
Text string `displayName:"Текст" displayName:"Текст поста"`
CreatedAt int64 `gorm:"autoCreateTime" displayName:"Дата публикации" cellType:"timestamp"`
}

View File

@@ -2,8 +2,11 @@ package services
import (
"app/internal/dal"
"app/internal/dialogs"
excel "app/internal/extras/excel"
"app/internal/models"
"errors"
"fmt"
"gorm.io/gen/field"
"gorm.io/gorm"
)
@@ -43,3 +46,10 @@ func (service *PostService) Count() (int64, error) {
amount, err := dal.Post.Count()
return amount, err
}
func (service *PostService) ExportToExcel() {
err := excel.ExportEntityToSpreadsheet("report.xlsx", "Посты", Post{}, service.GetAll)
if err != nil {
dialogs.ErrorDialog("Ошибка экспорта", fmt.Sprintf("Ошибка при экспорте данных: %s", err))
}
}