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)
blockID := ""
var blockID string
if blockIDArg := arg["blockID"]; nil != blockIDArg {
blockID = blockIDArg.(string)
}
var groupID string
if groupIDArg := arg["groupID"]; nil != groupIDArg {
groupID = groupIDArg.(string)
}
var previousID string
if nil != arg["previousID"] {
previousID = arg["previousID"].(string)
@ -310,7 +314,7 @@ func addAttributeViewBlocks(c *gin.Context) {
src := v.(map[string]interface{})
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 {
ret.Code = -1
ret.Msg = err.Error()

View file

@ -197,11 +197,28 @@ type View struct {
Groups []*View `json:"groups,omitempty"` // 分组视图列表
GroupItemIDs []string `json:"groupItemIds"` // 分组项目 ID 列表,用于维护分组中的所有项目
GroupCalc *GroupCalc `json:"groupCalc,omitempty"` // 分组计算规则
GroupName string `json:"groupName,omitempty"` // 分组名称
GroupValue string `json:"groupValue,omitempty"` // 分组值
GroupFolded bool `json:"groupFolded"` // 分组是否折叠
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 描述了分组计算规则和结果的结构。
type GroupCalc struct {
Field string `json:"field"` // 字段 ID
@ -283,9 +300,6 @@ type Viewable interface {
// GetGroupCalc 获取视图分组计算规则和结果。
GetGroupCalc() *GroupCalc
// SetGroupName 设置分组名称。
SetGroupName(name string)
// SetGroupFolded 设置分组是否折叠。
SetGroupFolded(folded bool)

View file

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

View file

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

View file

@ -1539,12 +1539,8 @@ func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query stri
// 如果存在分组的话渲染分组视图
var groups []av.Viewable
for _, groupView := range view.Groups {
switch groupView.LayoutType {
case av.LayoutTypeTable:
groupView.Table.Columns = view.Table.Columns
case av.LayoutTypeGallery:
groupView.Gallery.CardFields = view.Gallery.CardFields
}
groupView.Filters = view.Filters
groupView.Sorts = view.Sorts
groupViewable := sql.RenderView(attrView, groupView, query)
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
groupItemsMap := map[string][]av.Item{}
for _, item := range items {
value := item.GetValue(group.Field)
if value.IsEmpty() {
groupName = defaultGroupName
groupName = av.GroupValueDefault
groupItemsMap[groupName] = append(groupItemsMap[groupName], item)
continue
}
@ -1638,16 +1633,16 @@ func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
groupName = value.String(false)
case av.GroupMethodRangeNum:
if group.Range.NumStart > value.Number.Content || group.Range.NumEnd < value.Number.Content {
groupName = notInRange
groupName = av.GroupValueNotInRange
break
}
for rangeEnd <= group.Range.NumEnd && rangeEnd < value.Number.Content {
for rangeEnd <= group.Range.NumEnd && rangeEnd <= value.Number.Content {
rangeStart += 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))
}
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())
}
if defaultGroupName == name {
v.GroupValue = name
if av.GroupValueDefault == name {
name = fmt.Sprintf(Conf.language(264), groupKey.Name)
} else if notInRange == name {
} else if av.GroupValueNotInRange == name {
name = fmt.Sprintf(Conf.language(265))
}
v.Name = name
@ -2838,14 +2834,14 @@ func setAttributeViewColumnCalc(operation *Operation) (err error) {
}
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 {
return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
}
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
now := time.Now().UnixMilli()
@ -2874,14 +2870,14 @@ func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID,
if nil != src["content"] {
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
}
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
if !isDetached {
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}}
blockValues.Values = append(blockValues.Values, blockValue)
// 如果存在过滤条件,则将过滤条件应用到新添加的块上
view, _ := getAttrViewViewByBlockID(attrView, blockID)
if nil != view && 0 < len(view.Filters) && !ignoreFillFilter {
view, _ := getAttrViewViewByBlockID(attrView, blockID) // blockID 可能不传,所以这里的 view 可能为空,后面使用需要判空
var nearItem av.Item // 临近项
if nil != view && ((0 < len(view.Filters) && !ignoreFillFilter) || "" != groupID) {
// 存在过滤条件或者指定分组视图时,先获取临近项备用
viewable := sql.RenderView(attrView, view, "")
av.Filter(viewable, attrView)
av.Sort(viewable, attrView)
collection := viewable.(av.Collection)
items := collection.GetItems()
var nearItem av.Item
items := viewable.(av.Collection).GetItems()
if 0 < len(items) {
if "" != previousBlockID {
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 // 是否在同一个字段上同时存在过滤和排序
if 0 < len(view.Sorts) {
filterKeys, sortKeys := map[string]bool{}, map[string]bool{}
for _, f := range view.Filters {
filterKeys[f.Column] = true
}
sortKeys := map[string]bool{}
for _, s := range view.Sorts {
sortKeys[s.Column] = true
}
for key := range filterKeys {
if sortKeys[key] {
for k := range filterKeyIDs {
if sortKeys[k] {
sameKeyFilterSort = true
break
}
@ -3028,6 +3028,7 @@ func addAttributeViewBlock(now int64, avID, blockID, previousBlockID, addingBloc
bindBlockAv0(tx, avID, node, tree)
}
// 在所有视图上添加项目
for _, v := range attrView.Views {
if "" != previousBlockID {
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)
return
}
@ -4428,8 +4473,7 @@ func updateAttributeViewValue(tx *Transaction, attrView *av.AttributeView, keyID
func regenAttrViewViewGroups(attrView *av.AttributeView, keyID string) {
for _, view := range attrView.Views {
if nil != view.Group {
groupKey, _ := attrView.GetKey(view.Group.Field)
groupKey := getViewGroupKey(view, attrView)
if nil == groupKey {
continue
}
@ -4453,6 +4497,16 @@ func regenAttrViewViewGroups(attrView *av.AttributeView, keyID string) {
}
}
}
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) {

View file

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

View file

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