mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-02-02 18:28:41 +08:00
表格排序器增加关联表支持和字段映射
This commit is contained in:
parent
122af88992
commit
2a1e0c811c
@ -457,7 +457,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.633 h1:Yj8s35IjbgaHp4Ic9BZLVGWdN2gXBMtwYi1JJ+qYbrc=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.633 h1:Yj8s35IjbgaHp4Ic9BZLVGWdN2gXBMtwYi1JJ+qYbrc=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.633/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.633/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
|
@ -1,30 +1,95 @@
|
|||||||
|
// Package handler
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/text/gregex"
|
||||||
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
|
"github.com/gogf/gf/v2/util/gutil"
|
||||||
|
"hotgo/internal/consts"
|
||||||
"hotgo/internal/model/input/form"
|
"hotgo/internal/model/input/form"
|
||||||
|
"hotgo/utility/convert"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ISorter 排序器接口,实现该接口即可使用Handler匹配排序,支持多字段排序
|
// ISorter 排序器接口,实现该接口即可使用Handler匹配排序,支持多字段排序
|
||||||
type ISorter interface {
|
type ISorter interface {
|
||||||
GetSorters() []form.Sorter
|
GetSorters() []*form.Sorter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorter 排序器
|
// Sorter 排序器
|
||||||
func Sorter(in ISorter) func(m *gdb.Model) *gdb.Model {
|
func Sorter(in ISorter) func(m *gdb.Model) *gdb.Model {
|
||||||
return func(m *gdb.Model) *gdb.Model {
|
return func(m *gdb.Model) *gdb.Model {
|
||||||
hasSort := false
|
masterTable, ts := convert.GetModelTable(m)
|
||||||
|
fields, err := m.TableFields(masterTable)
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Panicf(m.GetCtx(), "failed to sorter TableFields err:%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
sorters := in.GetSorters()
|
sorters := in.GetSorters()
|
||||||
|
aliases := extractTableAliases(ts)
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
var newSorters []*form.Sorter
|
||||||
|
var removeIndex []int
|
||||||
|
for as, table := range aliases {
|
||||||
|
// 关联表
|
||||||
|
fds, err := m.TableFields(table)
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Panicf(m.GetCtx(), "failed to sorter TableFields err2:%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorter2 []*form.Sorter
|
||||||
|
for k, sorter := range sorters {
|
||||||
|
if gstr.HasPrefix(sorter.ColumnKey, as) {
|
||||||
|
sorter2 = append(sorter2, &form.Sorter{
|
||||||
|
ColumnKey: gstr.Replace(sorter.ColumnKey, as, ""),
|
||||||
|
Order: sorter.Order,
|
||||||
|
})
|
||||||
|
removeIndex = append(removeIndex, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sorter2) > 0 {
|
||||||
|
sorter2 = mappingAndFilterToTableFields(fds, sorter2)
|
||||||
|
for _, v := range sorter2 {
|
||||||
|
v.ColumnKey = fmt.Sprintf("`%v`.`%v`", as, v.ColumnKey)
|
||||||
|
}
|
||||||
|
newSorters = append(newSorters, sorter2...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除关联表字段
|
||||||
|
sorters = mappingAndFilterToTableFields(fields, removeSorterIndexes(sorters, removeIndex))
|
||||||
|
for _, v := range sorters {
|
||||||
|
v.ColumnKey = fmt.Sprintf("`%v`.`%v`", masterTable, v.ColumnKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
sorters = append(newSorters, sorters...)
|
||||||
|
} else {
|
||||||
|
// 单表
|
||||||
|
sorters = mappingAndFilterToTableFields(fields, sorters)
|
||||||
|
for _, v := range sorters {
|
||||||
|
v.ColumnKey = fmt.Sprintf("`%v`.`%v`", masterTable, v.ColumnKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSort := false
|
||||||
for _, sorter := range sorters {
|
for _, sorter := range sorters {
|
||||||
if len(sorter.ColumnKey) == 0 || !sorter.Sorter {
|
if len(sorter.ColumnKey) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch sorter.Order {
|
switch sorter.Order {
|
||||||
case "descend":
|
case "descend": // 降序
|
||||||
hasSort = true
|
hasSort = true
|
||||||
m = m.OrderDesc(sorter.ColumnKey)
|
m = m.OrderDesc(sorter.ColumnKey)
|
||||||
case "ascend":
|
case "ascend": // 升序
|
||||||
hasSort = true
|
hasSort = true
|
||||||
m = m.OrderAsc(sorter.ColumnKey)
|
m = m.OrderAsc(sorter.ColumnKey)
|
||||||
default:
|
default:
|
||||||
@ -32,10 +97,98 @@ func Sorter(in ISorter) func(m *gdb.Model) *gdb.Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不存在排序条件
|
if hasSort {
|
||||||
if !hasSort {
|
return m
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
return m
|
|
||||||
|
// 不存在排序条件,默认使用主表主键做降序排序
|
||||||
|
var pk string
|
||||||
|
for name, field := range fields {
|
||||||
|
if gstr.ContainsI(field.Key, consts.GenCodesIndexPK) {
|
||||||
|
pk = name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有主键
|
||||||
|
if len(pk) == 0 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存在别名,优先匹配别名
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
for as, table := range aliases {
|
||||||
|
if table == masterTable {
|
||||||
|
return m.OrderDesc(fmt.Sprintf("`%v`.`%v`", as, pk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m.OrderDesc(fmt.Sprintf("`%v`.`%v`", masterTable, pk))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractTableAliases 解析关联条件中的关联表别名
|
||||||
|
func extractTableAliases(ts string) map[string]string {
|
||||||
|
re := regexp.MustCompile("`?([^`\\s]+)`?\\s+AS\\s+`?([^`\\s]+)`?\\s")
|
||||||
|
matches := re.FindAllStringSubmatch(ts, -1)
|
||||||
|
|
||||||
|
result := make(map[string]string)
|
||||||
|
for _, match := range matches {
|
||||||
|
result[match[2]] = match[1]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeSorterIndexes 移除指定索引的排序器
|
||||||
|
func removeSorterIndexes(slice []*form.Sorter, indexes []int) []*form.Sorter {
|
||||||
|
removed := make([]*form.Sorter, 0)
|
||||||
|
indexMap := make(map[int]bool)
|
||||||
|
|
||||||
|
for _, index := range indexes {
|
||||||
|
indexMap[index] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, value := range slice {
|
||||||
|
if !indexMap[i] {
|
||||||
|
removed = append(removed, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
// mappingAndFilterToTableFields 将排序字段映射为实际的表字段
|
||||||
|
func mappingAndFilterToTableFields(fieldsMap map[string]*gdb.TableField, sorters []*form.Sorter) (ser []*form.Sorter) {
|
||||||
|
if len(fieldsMap) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields []string
|
||||||
|
for _, v := range sorters {
|
||||||
|
fields = append(fields, v.ColumnKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||||
|
for k := range fieldsMap {
|
||||||
|
fieldsKeyMap[k] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputFieldsArray = gstr.SplitAndTrim(gstr.Join(fields, ","), ",")
|
||||||
|
for _, field := range inputFieldsArray {
|
||||||
|
if _, ok := fieldsKeyMap[field]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gregex.IsMatchString(`^[\w\-]+$`, field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, field); foundKey != "" {
|
||||||
|
for _, v := range sorters {
|
||||||
|
if v.ColumnKey == field {
|
||||||
|
v.ColumnKey = foundKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sorters
|
||||||
|
}
|
||||||
|
96
server/internal/library/hgorm/handler/sorter_test.go
Normal file
96
server/internal/library/hgorm/handler/sorter_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Package handler
|
||||||
|
// @Link https://github.com/bufanyun/hotgo
|
||||||
|
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||||
|
// @Author Ms <133814250@qq.com>
|
||||||
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||||
|
package handler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||||
|
"github.com/gogf/gf/v2/os/gctx"
|
||||||
|
"hotgo/internal/dao"
|
||||||
|
"hotgo/internal/library/hgorm"
|
||||||
|
"hotgo/internal/library/hgorm/handler"
|
||||||
|
"hotgo/internal/model/input/form"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SorterInput struct {
|
||||||
|
form.Sorters
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSorterDefault 默认排序
|
||||||
|
func TestSorterDefault(t *testing.T) {
|
||||||
|
in := &SorterInput{} // 不存在排序条件,默认使用主表主键降序排序
|
||||||
|
|
||||||
|
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).Handler(handler.Sorter(in)).All()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSorter 多字段排序
|
||||||
|
func TestSorter(t *testing.T) {
|
||||||
|
in := &SorterInput{
|
||||||
|
Sorters: form.Sorters{
|
||||||
|
Sorters: []*form.Sorter{
|
||||||
|
{
|
||||||
|
ColumnKey: "id",
|
||||||
|
Order: "descend", // 降序
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ColumnKey: "categoryId", // 自动转换为下划线。categoryId -> category_id
|
||||||
|
Order: false, // 不参与排序
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ColumnKey: "created_at",
|
||||||
|
Order: "descend", // 降序
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).Handler(handler.Sorter(in)).All()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSorterJoinTable 关联表多字段排序
|
||||||
|
func TestSorterJoinTable(t *testing.T) {
|
||||||
|
in := &SorterInput{
|
||||||
|
Sorters: form.Sorters{
|
||||||
|
Sorters: []*form.Sorter{
|
||||||
|
{
|
||||||
|
ColumnKey: "id",
|
||||||
|
Order: "descend", // 降序
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ColumnKey: "categoryId", // 自动转换为下划线。categoryId -> category_id
|
||||||
|
Order: false, // 不参与排序
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ColumnKey: "created_at",
|
||||||
|
Order: "descend", // 降序
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ColumnKey: "testCategoryName", // 自动识别关联表别名。 testCategoryName -> testCategory.name
|
||||||
|
Order: "ascend", // 升序
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).
|
||||||
|
LeftJoin(hgorm.GenJoinOnRelation(
|
||||||
|
dao.SysGenCurdDemo.Table(), dao.SysGenCurdDemo.Columns().CategoryId, // 主表表名,关联字段
|
||||||
|
dao.TestCategory.Table(), "testCategory", dao.TestCategory.Columns().Id, // 关联表表名,别名,关联字段
|
||||||
|
)...).
|
||||||
|
Handler(handler.Sorter(in)).All()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
@ -7,15 +7,14 @@ package form
|
|||||||
|
|
||||||
// Sorter 排序器,兼容naiveUI
|
// Sorter 排序器,兼容naiveUI
|
||||||
type Sorter struct {
|
type Sorter struct {
|
||||||
ColumnKey string `json:"columnKey" dc:"排序字段"`
|
ColumnKey string `json:"columnKey" dc:"排序字段"`
|
||||||
Sorter bool `json:"sorter" dc:"是否需要排序"`
|
Order interface{} `json:"order" dc:"排序方式 descend|ascend|false"`
|
||||||
Order interface{} `json:"order" dc:"排序方式 descend|ascend|false"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sorters struct {
|
type Sorters struct {
|
||||||
Sorters []Sorter `json:"sorters" dc:"排序器"`
|
Sorters []*Sorter `json:"sorters" dc:"排序器"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sorters) GetSorters() []Sorter {
|
func (s *Sorters) GetSorters() []*Sorter {
|
||||||
return s.Sorters
|
return s.Sorters
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
package convert
|
package convert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
"hotgo/utility/validate"
|
"hotgo/utility/validate"
|
||||||
"reflect"
|
"reflect"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -17,6 +20,33 @@ var (
|
|||||||
fieldTags = []string{"json"} // 实体字段名称映射
|
fieldTags = []string{"json"} // 实体字段名称映射
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetModelTable 获取模型中的表定义
|
||||||
|
func GetModelTable(m *gdb.Model) (tablesInit, tables string) {
|
||||||
|
if m == nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(m).Elem()
|
||||||
|
t := reflect.TypeOf(m).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
val := v.Field(i)
|
||||||
|
|
||||||
|
if field.Name == "tablesInit" {
|
||||||
|
tablesInit = reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().String()
|
||||||
|
tablesInit = gstr.Replace(tablesInit, "`", "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Name == "tables" {
|
||||||
|
tables = reflect.NewAt(val.Type(), unsafe.Pointer(val.UnsafeAddr())).Elem().String()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetMapKeys 获取map的所有key
|
// GetMapKeys 获取map的所有key
|
||||||
func GetMapKeys[K comparable](m map[K]any) []K {
|
func GetMapKeys[K comparable](m map[K]any) []K {
|
||||||
j := 0
|
j := 0
|
||||||
|
@ -487,6 +487,7 @@ export const columns = [
|
|||||||
render(row) {
|
render(row) {
|
||||||
return formatToDate(row.activityAt);
|
return formatToDate(row.activityAt);
|
||||||
},
|
},
|
||||||
|
sorter: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user