feat: excel export (only primitive types)
This commit is contained in:
@@ -36,6 +36,11 @@ export function Delete(item: $models.Post): Promise<$models.Post> & { cancel():
|
|||||||
return $typingPromise;
|
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 } {
|
export function GetAll(): Promise<($models.Post | null)[]> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(65691059) as any;
|
let $resultPromise = $Call.ByID(65691059) as any;
|
||||||
let $typingPromise = $resultPromise.then(($result) => {
|
let $typingPromise = $resultPromise.then(($result) => {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const doGreet = () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log(await PostService.GetById(5))
|
console.log(await PostService.GetById(5))
|
||||||
|
await PostService.ExportToExcel()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
package excel
|
package excel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"app/internal/services"
|
"app/internal/dialogs"
|
||||||
|
"fmt"
|
||||||
"github.com/xuri/excelize/v2"
|
"github.com/xuri/excelize/v2"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TableHeaders struct {
|
type TableHeaders struct {
|
||||||
Headers []string
|
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()
|
file := excelize.NewFile()
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := file.Close(); err != nil {
|
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
|
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 {
|
func ExportHeaders(entity any) TableHeaders {
|
||||||
headers := TableHeaders{}
|
headers := TableHeaders{}
|
||||||
v := reflect.TypeOf(entity)
|
v := reflect.TypeOf(entity)
|
||||||
@@ -30,8 +122,98 @@ func ExportHeaders(entity any) TableHeaders {
|
|||||||
field := v.Field(i)
|
field := v.Field(i)
|
||||||
displayName := field.Tag.Get("displayName")
|
displayName := field.Tag.Get("displayName")
|
||||||
if displayName != "" {
|
if displayName != "" {
|
||||||
|
headers.Headers = append(headers.Headers, displayName)
|
||||||
|
} else {
|
||||||
|
headers.IgnoredFieldsIndices = append(headers.IgnoredFieldsIndices, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return headers
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ var Entities = []any{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey" displayName:"Номер"`
|
||||||
Text string `displayName:"Текст"`
|
Text string `displayName:"Текст" displayName:"Текст поста"`
|
||||||
CreatedAt int64 `gorm:"autoCreateTime" displayName:"Дата публикации" cellType:"timestamp"`
|
CreatedAt int64 `gorm:"autoCreateTime" displayName:"Дата публикации" cellType:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"app/internal/dal"
|
"app/internal/dal"
|
||||||
|
"app/internal/dialogs"
|
||||||
|
excel "app/internal/extras/excel"
|
||||||
"app/internal/models"
|
"app/internal/models"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"gorm.io/gen/field"
|
"gorm.io/gen/field"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -43,3 +46,10 @@ func (service *PostService) Count() (int64, error) {
|
|||||||
amount, err := dal.Post.Count()
|
amount, err := dal.Post.Count()
|
||||||
return amount, err
|
return amount, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service *PostService) ExportToExcel() {
|
||||||
|
err := excel.ExportEntityToSpreadsheet("report.xlsx", "Посты", Post{}, service.GetAll)
|
||||||
|
if err != nil {
|
||||||
|
dialogs.ErrorDialog("Ошибка экспорта", fmt.Sprintf("Ошибка при экспорте данных: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user