This commit is contained in:
Daniel 2025-07-27 17:08:22 +08:00
parent 759c12be06
commit cff71aa720
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
7 changed files with 139 additions and 72 deletions

View file

@ -292,10 +292,14 @@ func addAttributeViewBlocks(c *gin.Context) {
} }
avID := arg["avID"].(string) avID := arg["avID"].(string)
blockID := "" var blockID string
if blockIDArg := arg["blockID"]; nil != blockIDArg { if blockIDArg := arg["blockID"]; nil != blockIDArg {
blockID = blockIDArg.(string) blockID = blockIDArg.(string)
} }
var groupID string
if groupIDArg := arg["groupID"]; nil != groupIDArg {
groupID = groupIDArg.(string)
}
var previousID string var previousID string
if nil != arg["previousID"] { if nil != arg["previousID"] {
previousID = arg["previousID"].(string) previousID = arg["previousID"].(string)
@ -310,7 +314,7 @@ func addAttributeViewBlocks(c *gin.Context) {
src := v.(map[string]interface{}) src := v.(map[string]interface{})
srcs = append(srcs, src) srcs = append(srcs, src)
} }
err := model.AddAttributeViewBlock(nil, srcs, avID, blockID, previousID, ignoreFillFilter) err := model.AddAttributeViewBlock(nil, srcs, avID, blockID, groupID, previousID, ignoreFillFilter)
if err != nil { if err != nil {
ret.Code = -1 ret.Code = -1
ret.Msg = err.Error() ret.Msg = err.Error()

View file

@ -192,14 +192,31 @@ type View struct {
Gallery *LayoutGallery `json:"gallery,omitempty"` // 卡片布局 Gallery *LayoutGallery `json:"gallery,omitempty"` // 卡片布局
ItemIDs []string `json:"itemIds,omitempty"` // 项目 ID 列表,用于维护所有项目 ItemIDs []string `json:"itemIds,omitempty"` // 项目 ID 列表,用于维护所有项目
Group *ViewGroup `json:"group,omitempty"` // 分组规则 Group *ViewGroup `json:"group,omitempty"` // 分组规则
GroupUpdated int64 `json:"groupUpdated"` // 分组规则更新时间戳 GroupUpdated int64 `json:"groupUpdated"` // 分组规则更新时间戳
Groups []*View `json:"groups,omitempty"` // 分组视图列表 Groups []*View `json:"groups,omitempty"` // 分组视图列表
GroupItemIDs []string `json:"groupItemIds"` // 分组项目 ID 列表,用于维护分组中的所有项目 GroupItemIDs []string `json:"groupItemIds"` // 分组项目 ID 列表,用于维护分组中的所有项目
GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则 GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则
GroupName string `json:"groupName,omitempty"` // 分组名称 GroupValue string `json:"groupValue,omitempty"` // 分组值
GroupFolded bool `json:"groupFolded"` // 分组是否折叠 GroupFolded bool `json:"groupFolded"` // 分组是否折叠
GroupHidden int `json:"groupHidden"` // 分组是否隐藏0显示1空白隐藏2手动隐藏 GroupHidden int `json:"groupHidden"` // 分组是否隐藏0显示1空白隐藏2手动隐藏
}
const (
GroupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组)
GroupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值)
)
// GetGroup 获取指定分组 ID 的分组视图。
func (view *View) GetGroup(groupID string) *View {
if nil == view.Groups {
return nil
}
for _, group := range view.Groups {
if group.ID == groupID {
return group
}
}
} }
// GroupCalc 描述了分组计算规则和结果的结构。 // GroupCalc 描述了分组计算规则和结果的结构。
@ -283,9 +300,6 @@ type Viewable interface {
// GetGroupCalc 获取视图分组计算规则和结果。 // GetGroupCalc 获取视图分组计算规则和结果。
GetGroupCalc() *GroupCalc GetGroupCalc() *GroupCalc
// SetGroupName 设置分组名称。
SetGroupName(name string)
// SetGroupFolded 设置分组是否折叠。 // SetGroupFolded 设置分组是否折叠。
SetGroupFolded(folded bool) SetGroupFolded(folded bool)

View file

@ -866,8 +866,8 @@ func (filter *ViewFilter) GetAffectValue(key *Key, defaultVal *Value) (ret *Valu
return return
} }
} }
// 没有默认值则使用过滤条件的值
// 没有默认值则使用过滤条件的值
switch filter.Value.Type { switch filter.Value.Type {
case KeyTypeBlock: case KeyTypeBlock:
switch filter.Operator { switch filter.Operator {

View file

@ -66,7 +66,6 @@ type BaseInstance struct {
Groups []Viewable `json:"groups,omitempty"` // 分组实例列表 Groups []Viewable `json:"groups,omitempty"` // 分组实例列表
GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则和结果 GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则和结果
GroupName string `json:"groupName,omitempty"` // 分组名称
GroupFolded bool `json:"groupFolded"` // 分组是否折叠 GroupFolded bool `json:"groupFolded"` // 分组是否折叠
GroupHidden int `json:"groupHidden"` // 分组是否隐藏0显示1空白隐藏2手动隐藏 GroupHidden int `json:"groupHidden"` // 分组是否隐藏0显示1空白隐藏2手动隐藏
} }
@ -91,7 +90,6 @@ func NewViewBaseInstance(view *View) *BaseInstance {
Sorts: view.Sorts, Sorts: view.Sorts,
Group: view.Group, Group: view.Group,
GroupCalc: view.GroupCalc, GroupCalc: view.GroupCalc,
GroupName: view.GroupName,
GroupFolded: view.GroupFolded, GroupFolded: view.GroupFolded,
GroupHidden: view.GroupHidden, GroupHidden: view.GroupHidden,
ShowIcon: showIcon, ShowIcon: showIcon,
@ -119,10 +117,6 @@ func (baseInstance *BaseInstance) GetGroupCalc() *GroupCalc {
return baseInstance.GroupCalc return baseInstance.GroupCalc
} }
func (baseInstance *BaseInstance) SetGroupName(name string) {
baseInstance.GroupName = name
}
func (baseInstance *BaseInstance) SetGroupFolded(folded bool) { func (baseInstance *BaseInstance) SetGroupFolded(folded bool) {
baseInstance.GroupFolded = folded baseInstance.GroupFolded = folded
} }

View file

@ -1539,12 +1539,8 @@ func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query stri
// 如果存在分组的话渲染分组视图 // 如果存在分组的话渲染分组视图
var groups []av.Viewable var groups []av.Viewable
for _, groupView := range view.Groups { for _, groupView := range view.Groups {
switch groupView.LayoutType { groupView.Filters = view.Filters
case av.LayoutTypeTable: groupView.Sorts = view.Sorts
groupView.Table.Columns = view.Table.Columns
case av.LayoutTypeGallery:
groupView.Gallery.CardFields = view.Gallery.CardFields
}
groupViewable := sql.RenderView(attrView, groupView, query) groupViewable := sql.RenderView(attrView, groupView, query)
err = renderViewableInstance(groupViewable, view, attrView, page, pageSize) err = renderViewableInstance(groupViewable, view, attrView, page, pageSize)
@ -1622,13 +1618,12 @@ func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
}) })
} }
const defaultGroupName, notInRange = "_@default@_", "_@notInRange@_"
var groupName string var groupName string
groupItemsMap := map[string][]av.Item{} groupItemsMap := map[string][]av.Item{}
for _, item := range items { for _, item := range items {
value := item.GetValue(group.Field) value := item.GetValue(group.Field)
if value.IsEmpty() { if value.IsEmpty() {
groupName = defaultGroupName groupName = av.GroupValueDefault
groupItemsMap[groupName] = append(groupItemsMap[groupName], item) groupItemsMap[groupName] = append(groupItemsMap[groupName], item)
continue continue
} }
@ -1638,16 +1633,16 @@ func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
groupName = value.String(false) groupName = value.String(false)
case av.GroupMethodRangeNum: case av.GroupMethodRangeNum:
if group.Range.NumStart > value.Number.Content || group.Range.NumEnd < value.Number.Content { if group.Range.NumStart > value.Number.Content || group.Range.NumEnd < value.Number.Content {
groupName = notInRange groupName = av.GroupValueNotInRange
break break
} }
for rangeEnd <= group.Range.NumEnd && rangeEnd < value.Number.Content { for rangeEnd <= group.Range.NumEnd && rangeEnd <= value.Number.Content {
rangeStart += group.Range.NumStep rangeStart += group.Range.NumStep
rangeEnd += group.Range.NumStep rangeEnd += group.Range.NumStep
} }
if rangeStart <= value.Number.Content && rangeEnd >= value.Number.Content { if rangeStart <= value.Number.Content && rangeEnd > value.Number.Content {
groupName = fmt.Sprintf("%s - %s", strconv.FormatFloat(rangeStart, 'f', -1, 64), strconv.FormatFloat(rangeEnd, 'f', -1, 64)) groupName = fmt.Sprintf("%s - %s", strconv.FormatFloat(rangeStart, 'f', -1, 64), strconv.FormatFloat(rangeEnd, 'f', -1, 64))
} }
case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative: case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative:
@ -1727,9 +1722,10 @@ func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
v.GroupItemIDs = append(v.GroupItemIDs, item.GetID()) v.GroupItemIDs = append(v.GroupItemIDs, item.GetID())
} }
if defaultGroupName == name { v.GroupValue = name
if av.GroupValueDefault == name {
name = fmt.Sprintf(Conf.language(264), groupKey.Name) name = fmt.Sprintf(Conf.language(264), groupKey.Name)
} else if notInRange == name { } else if av.GroupValueNotInRange == name {
name = fmt.Sprintf(Conf.language(265)) name = fmt.Sprintf(Conf.language(265))
} }
v.Name = name v.Name = name
@ -2838,14 +2834,14 @@ func setAttributeViewColumnCalc(operation *Operation) (err error) {
} }
func (tx *Transaction) doInsertAttrViewBlock(operation *Operation) (ret *TxErr) { func (tx *Transaction) doInsertAttrViewBlock(operation *Operation) (ret *TxErr) {
err := AddAttributeViewBlock(tx, operation.Srcs, operation.AvID, operation.BlockID, operation.PreviousID, operation.IgnoreFillFilterVal) err := AddAttributeViewBlock(tx, operation.Srcs, operation.AvID, operation.BlockID, operation.GroupID, operation.PreviousID, operation.IgnoreFillFilterVal)
if err != nil { if err != nil {
return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()} return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
} }
return return
} }
func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID, blockID, previousBlockID string, ignoreFillFilter bool) (err error) { func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID, blockID, groupID, previousBlockID string, ignoreFillFilter bool) (err error) {
slices.Reverse(srcs) // https://github.com/siyuan-note/siyuan/issues/11286 slices.Reverse(srcs) // https://github.com/siyuan-note/siyuan/issues/11286
now := time.Now().UnixMilli() now := time.Now().UnixMilli()
@ -2874,14 +2870,14 @@ func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID,
if nil != src["content"] { if nil != src["content"] {
srcContent = src["content"].(string) srcContent = src["content"].(string)
} }
if avErr := addAttributeViewBlock(now, avID, blockID, previousBlockID, srcID, srcContent, isDetached, ignoreFillFilter, tree, tx); nil != avErr { if avErr := addAttributeViewBlock(now, avID, blockID, groupID, previousBlockID, srcID, srcContent, isDetached, ignoreFillFilter, tree, tx); nil != avErr {
return avErr return avErr
} }
} }
return return
} }
func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBlockID, addingBlockContent string, isDetached, ignoreFillFilter bool, tree *parse.Tree, tx *Transaction) (err error) { func addAttributeViewBlock(now int64, avID, blockID, groupID, previousBlockID, addingBlockID, addingBlockContent string, isDetached, ignoreFillFilter bool, tree *parse.Tree, tx *Transaction) (err error) {
var node *ast.Node var node *ast.Node
if !isDetached { if !isDetached {
node = treenode.GetNodeInTree(tree, addingBlockID) node = treenode.GetNodeInTree(tree, addingBlockID)
@ -2935,17 +2931,14 @@ func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBloc
Block: &av.ValueBlock{ID: addingBlockID, Icon: blockIcon, Content: addingBlockContent, Created: now, Updated: now}} Block: &av.ValueBlock{ID: addingBlockID, Icon: blockIcon, Content: addingBlockContent, Created: now, Updated: now}}
blockValues.Values = append(blockValues.Values, blockValue) blockValues.Values = append(blockValues.Values, blockValue)
// 如果存在过滤条件,则将过滤条件应用到新添加的块上 view, _ := getAttrViewViewByBlockID(attrView, blockID) // blockID 可能不传,所以这里的 view 可能为空,后面使用需要判空
view, _ := getAttrViewViewByBlockID(attrView, blockID) var nearItem av.Item // 临近项
if nil != view && 0 < len(view.Filters) && !ignoreFillFilter { if nil != view && ((0 < len(view.Filters) && !ignoreFillFilter) || "" != groupID) {
// 存在过滤条件或者指定分组视图时,先获取临近项备用
viewable := sql.RenderView(attrView, view, "") viewable := sql.RenderView(attrView, view, "")
av.Filter(viewable, attrView) av.Filter(viewable, attrView)
av.Sort(viewable, attrView) av.Sort(viewable, attrView)
items := viewable.(av.Collection).GetItems()
collection := viewable.(av.Collection)
items := collection.GetItems()
var nearItem av.Item
if 0 < len(items) { if 0 < len(items) {
if "" != previousBlockID { if "" != previousBlockID {
for _, row := range items { for _, row := range items {
@ -2960,19 +2953,26 @@ func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBloc
} }
} }
} }
}
filterKeyIDs := map[string]bool{}
if nil != view {
for _, f := range view.Filters {
filterKeyIDs[f.Column] = true
}
}
// 如果存在过滤条件,则将过滤条件应用到新添加的块上
if nil != view && 0 < len(view.Filters) && !ignoreFillFilter {
sameKeyFilterSort := false // 是否在同一个字段上同时存在过滤和排序 sameKeyFilterSort := false // 是否在同一个字段上同时存在过滤和排序
if 0 < len(view.Sorts) { if 0 < len(view.Sorts) {
filterKeys, sortKeys := map[string]bool{}, map[string]bool{} sortKeys := map[string]bool{}
for _, f := range view.Filters {
filterKeys[f.Column] = true
}
for _, s := range view.Sorts { for _, s := range view.Sorts {
sortKeys[s.Column] = true sortKeys[s.Column] = true
} }
for key := range filterKeys { for k := range filterKeyIDs {
if sortKeys[key] { if sortKeys[k] {
sameKeyFilterSort = true sameKeyFilterSort = true
break break
} }
@ -3028,6 +3028,7 @@ func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBloc
bindBlockAv0(tx, avID, node, tree) bindBlockAv0(tx, avID, node, tree)
} }
// 在所有视图上添加项目
for _, v := range attrView.Views { for _, v := range attrView.Views {
if "" != previousBlockID { if "" != previousBlockID {
changed := false changed := false
@ -3046,6 +3047,50 @@ func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBloc
} }
} }
// 如果存在分组条件,则将分组条件应用到新添加的块上
groupKey := getViewGroupKey(view, attrView)
if nil != view && nil != groupKey {
if !filterKeyIDs[groupKey.ID] /* 过滤条件应用过的话就不重复处理了 */ && "" != groupID {
if groupView := view.GetGroup(groupID); nil != groupView {
if keyValues, _ := attrView.GetKeyValues(groupKey.ID); nil != keyValues {
var newValue, defaultVal *av.Value
if nil != nearItem {
defaultVal = nearItem.GetValue(groupKey.ID)
}
if nil != defaultVal {
newValue = defaultVal.Clone()
} else {
newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, blockID, groupKey.Type)
}
if av.KeyTypeBlock == newValue.Type {
// 如果是主键的话前面已经添加过了,这里仅修改内容
blockValue.Block.Content = newValue.Block.Content
} else {
newValue.ID = ast.NewNodeID()
newValue.CreatedAt = util.CurrentTimeMillis()
newValue.UpdatedAt = newValue.CreatedAt + 1000
newValue.KeyID = keyValues.Key.ID
newValue.BlockID = addingBlockID
newValue.IsDetached = isDetached
if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
// 因为单选或多选只能按选项分组,并且可能存在空白分组(前面可能找不到临近项) ,所以单选或多选类型的分组字段使用分组值内容对应的选项
if opt := groupKey.GetOption(groupView.GroupValue); nil != opt {
newValue.MSelect[0].Content = opt.Name
newValue.MSelect[0].Color = opt.Color
}
}
keyValues.Values = append(keyValues.Values, newValue)
}
}
}
}
regenAttrViewViewGroups(attrView, groupKey.ID)
}
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
return return
} }
@ -4428,33 +4473,42 @@ func updateAttributeViewValue(tx *Transaction, attrView *av.AttributeView, keyID
func regenAttrViewViewGroups(attrView *av.AttributeView, keyID string) { func regenAttrViewViewGroups(attrView *av.AttributeView, keyID string) {
for _, view := range attrView.Views { for _, view := range attrView.Views {
if nil != view.Group { groupKey := getViewGroupKey(view, attrView)
groupKey, _ := attrView.GetKey(view.Group.Field) if nil == groupKey {
if nil == groupKey { continue
continue }
}
if av.KeyTypeTemplate != groupKey.Type && view.Group.Field != keyID { if av.KeyTypeTemplate != groupKey.Type && view.Group.Field != keyID {
continue continue
} }
genAttrViewViewGroups(view, attrView) genAttrViewViewGroups(view, attrView)
for _, g := range view.Groups { for _, g := range view.Groups {
if view.Group.HideEmpty { if view.Group.HideEmpty {
if 2 != g.GroupHidden && 1 > len(g.GroupItemIDs) { if 2 != g.GroupHidden && 1 > len(g.GroupItemIDs) {
g.GroupHidden = 1 g.GroupHidden = 1
} }
} else { } else {
if 2 != g.GroupHidden { if 2 != g.GroupHidden {
g.GroupHidden = 0 g.GroupHidden = 0
}
} }
} }
} }
} }
} }
func getViewGroupKey(view *av.View, attrView *av.AttributeView) *av.Key {
if nil == view.Group {
return nil
}
if "" == view.Group.Field {
return nil
}
ret, _ := attrView.GetKey(view.Group.Field)
return ret
}
func unbindBlockAv(tx *Transaction, avID, blockID string) { func unbindBlockAv(tx *Transaction, avID, blockID string) {
node, tree, err := getNodeByBlockID(tx, blockID) node, tree, err := getNodeByBlockID(tx, blockID)
if err != nil { if err != nil {

View file

@ -986,7 +986,7 @@ func DuplicateDoc(tree *parse.Tree) {
AddAttributeViewBlock(nil, []map[string]interface{}{{ AddAttributeViewBlock(nil, []map[string]interface{}{{
"id": n.ID, "id": n.ID,
"isDetached": false, "isDetached": false,
}}, avID, "", "", false) }}, avID, "", "", "", false)
ReloadAttrView(avID) ReloadAttrView(avID)
} }
return ast.WalkContinue return ast.WalkContinue

View file

@ -1096,7 +1096,7 @@ func (tx *Transaction) doLargeInsert(previousID string) (ret *TxErr) {
AddAttributeViewBlock(tx, []map[string]interface{}{{ AddAttributeViewBlock(tx, []map[string]interface{}{{
"id": insertedNode.ID, "id": insertedNode.ID,
"isDetached": false, "isDetached": false,
}}, avID, "", previousID, false) }}, avID, "", "", previousID, false)
ReloadAttrView(avID) ReloadAttrView(avID)
} }
@ -1275,7 +1275,7 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
AddAttributeViewBlock(tx, []map[string]interface{}{{ AddAttributeViewBlock(tx, []map[string]interface{}{{
"id": insertedNode.ID, "id": insertedNode.ID,
"isDetached": false, "isDetached": false,
}}, avID, "", previousID, false) }}, avID, "", "", previousID, false)
ReloadAttrView(avID) ReloadAttrView(avID)
} }
@ -1692,6 +1692,7 @@ type Operation struct {
BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID
RemoveDest bool `json:"removeDest"` // 属性视图删除关联目标 RemoveDest bool `json:"removeDest"` // 属性视图删除关联目标
Layout av.LayoutType `json:"layout"` // 属性视图布局类型 Layout av.LayoutType `json:"layout"` // 属性视图布局类型
GroupID string `json:"groupID"` // 属性视图分组视图 ID
} }
type Transaction struct { type Transaction struct {