diff --git a/kernel/av/av.go b/kernel/av/av.go index be6006072..b782236bd 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -61,6 +61,8 @@ const ( KeyTypeSelect KeyType = "select" KeyTypeMSelect KeyType = "mSelect" KeyTypeURL KeyType = "url" + KeyTypeEmail KeyType = "email" + KeyTypePhone KeyType = "phone" ) // Key 描述了属性视图属性列的基础结构。 @@ -100,6 +102,8 @@ type Value struct { Date *ValueDate `json:"date,omitempty"` MSelect []*ValueSelect `json:"mSelect,omitempty"` URL *ValueURL `json:"url,omitempty"` + Email *ValueEmail `json:"email,omitempty"` + Phone *ValuePhone `json:"phone,omitempty"` } func (value *Value) ToJSONString() string { @@ -231,6 +235,14 @@ type ValueURL struct { Content string `json:"content"` } +type ValueEmail struct { + Content string `json:"content"` +} + +type ValuePhone struct { + Content string `json:"content"` +} + // View 描述了视图的结构。 type View struct { ID string `json:"id"` // 视图 ID diff --git a/kernel/av/table.go b/kernel/av/table.go index f192f5742..a9cdfe34c 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -126,6 +126,12 @@ func (value *Value) Compare(other *Value) int { if nil != value.URL && nil != other.URL { return strings.Compare(value.URL.Content, other.URL.Content) } + if nil != value.Email && nil != other.Email { + return strings.Compare(value.Email.Content, other.Email.Content) + } + if nil != value.Phone && nil != other.Phone { + return strings.Compare(value.Phone.Content, other.Phone.Content) + } return 0 } @@ -280,6 +286,48 @@ func (value *Value) CompareOperator(other *Value, operator FilterOperator) bool } } + if nil != value.Email && nil != other.Email { + switch operator { + case FilterOperatorIsEqual: + return value.Email.Content == other.Email.Content + case FilterOperatorIsNotEqual: + return value.Email.Content != other.Email.Content + case FilterOperatorContains: + return strings.Contains(value.Email.Content, other.Email.Content) + case FilterOperatorDoesNotContain: + return !strings.Contains(value.Email.Content, other.Email.Content) + case FilterOperatorStartsWith: + return strings.HasPrefix(value.Email.Content, other.Email.Content) + case FilterOperatorEndsWith: + return strings.HasSuffix(value.Email.Content, other.Email.Content) + case FilterOperatorIsEmpty: + return "" == strings.TrimSpace(value.Email.Content) + case FilterOperatorIsNotEmpty: + return "" != strings.TrimSpace(value.Email.Content) + } + } + + if nil != value.Phone && nil != other.Phone { + switch operator { + case FilterOperatorIsEqual: + return value.Phone.Content == other.Phone.Content + case FilterOperatorIsNotEqual: + return value.Phone.Content != other.Phone.Content + case FilterOperatorContains: + return strings.Contains(value.Phone.Content, other.Phone.Content) + case FilterOperatorDoesNotContain: + return !strings.Contains(value.Phone.Content, other.Phone.Content) + case FilterOperatorStartsWith: + return strings.HasPrefix(value.Phone.Content, other.Phone.Content) + case FilterOperatorEndsWith: + return strings.HasSuffix(value.Phone.Content, other.Phone.Content) + case FilterOperatorIsEmpty: + return "" == strings.TrimSpace(value.Phone.Content) + case FilterOperatorIsNotEmpty: + return "" != strings.TrimSpace(value.Phone.Content) + } + } + return true } @@ -413,6 +461,10 @@ func (table *Table) CalcCols() { table.calcColMSelect(col, i) case KeyTypeURL: table.calcColURL(col, i) + case KeyTypeEmail: + table.calcColEmail(col, i) + case KeyTypePhone: + table.calcColPhone(col, i) } } } @@ -910,6 +962,132 @@ func (table *Table) calcColURL(col *TableColumn, colIndex int) { } } +func (table *Table) calcColEmail(col *TableColumn, colIndex int) { + switch col.Calc.Operator { + case CalcOperatorCountAll: + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(len(table.Rows)), NumberFormatNone)} + case CalcOperatorCountValues: + countValues := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Email && "" != row.Cells[colIndex].Value.Email.Content { + countValues++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)} + case CalcOperatorCountUniqueValues: + countUniqueValues := 0 + uniqueValues := map[string]bool{} + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Email && "" != row.Cells[colIndex].Value.Email.Content { + if !uniqueValues[row.Cells[colIndex].Value.Email.Content] { + uniqueValues[row.Cells[colIndex].Value.Email.Content] = true + countUniqueValues++ + } + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues), NumberFormatNone)} + case CalcOperatorCountEmpty: + countEmpty := 0 + for _, row := range table.Rows { + if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Email || "" == row.Cells[colIndex].Value.Email.Content { + countEmpty++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty), NumberFormatNone)} + case CalcOperatorCountNotEmpty: + countNotEmpty := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Email && "" != row.Cells[colIndex].Value.Email.Content { + countNotEmpty++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty), NumberFormatNone)} + case CalcOperatorPercentEmpty: + countEmpty := 0 + for _, row := range table.Rows { + if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Email || "" == row.Cells[colIndex].Value.Email.Content { + countEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + case CalcOperatorPercentNotEmpty: + countNotEmpty := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Email && "" != row.Cells[colIndex].Value.Email.Content { + countNotEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + } +} + +func (table *Table) calcColPhone(col *TableColumn, colIndex int) { + switch col.Calc.Operator { + case CalcOperatorCountAll: + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(len(table.Rows)), NumberFormatNone)} + case CalcOperatorCountValues: + countValues := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Phone && "" != row.Cells[colIndex].Value.Phone.Content { + countValues++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)} + case CalcOperatorCountUniqueValues: + countUniqueValues := 0 + uniqueValues := map[string]bool{} + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Phone && "" != row.Cells[colIndex].Value.Phone.Content { + if !uniqueValues[row.Cells[colIndex].Value.Phone.Content] { + uniqueValues[row.Cells[colIndex].Value.Phone.Content] = true + countUniqueValues++ + } + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues), NumberFormatNone)} + case CalcOperatorCountEmpty: + countEmpty := 0 + for _, row := range table.Rows { + if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Phone || "" == row.Cells[colIndex].Value.Phone.Content { + countEmpty++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty), NumberFormatNone)} + case CalcOperatorCountNotEmpty: + countNotEmpty := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Phone && "" != row.Cells[colIndex].Value.Phone.Content { + countNotEmpty++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty), NumberFormatNone)} + case CalcOperatorPercentEmpty: + countEmpty := 0 + for _, row := range table.Rows { + if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Phone || "" == row.Cells[colIndex].Value.Phone.Content { + countEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + case CalcOperatorPercentNotEmpty: + countNotEmpty := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Phone && "" != row.Cells[colIndex].Value.Phone.Content { + countNotEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + } +} + func (table *Table) calcColBlock(col *TableColumn, colIndex int) { switch col.Calc.Operator { case CalcOperatorCountAll: diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 064265059..2efca257a 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -727,7 +727,7 @@ func addAttributeViewColumn(operation *Operation) (err error) { keyType := av.KeyType(operation.Typ) switch keyType { - case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL: + case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone: key := av.NewKey(operation.ID, operation.Name, keyType) attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: key}) @@ -757,7 +757,7 @@ func updateAttributeViewColumn(operation *Operation) (err error) { colType := av.KeyType(operation.Typ) switch colType { - case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL: + case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone: for _, keyValues := range attrView.KeyValues { if keyValues.Key.ID == operation.ID { keyValues.Key.Name = operation.Name