Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见. Co-authored-by: Cursor <cursoragent@cursor.com>
166 lines
3.7 KiB
Go
166 lines
3.7 KiB
Go
package blueprint
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
)
|
||
|
||
// defaultImportExportOps Z9:业务表默认允许导入/导出。
|
||
var defaultImportExportOps = []string{"import", "export"}
|
||
|
||
// defaultListActions Z9:列表页默认工具栏(含导入)。
|
||
var defaultListActions = []string{"create", "edit", "delete", "export", "import", "refresh"}
|
||
|
||
// EnsureDefaultImportExport 发布/合并兜底(Z9d):
|
||
// 可写业务 resource 缺 import/export 则补上;list 页 actions 缺 import 则补上。
|
||
// 跳过:只读 resource(仅 list/get);meta.ui.disable_default_import == true。
|
||
func (bp *Blueprint) EnsureDefaultImportExport() {
|
||
if bp == nil {
|
||
return
|
||
}
|
||
if disableDefaultImport(bp) {
|
||
return
|
||
}
|
||
for i := range bp.Apis.Resources {
|
||
r := &bp.Apis.Resources[i]
|
||
if resourceReadOnly(r.Operations) {
|
||
continue
|
||
}
|
||
r.Operations = ensureOpList(r.Operations, defaultImportExportOps...)
|
||
}
|
||
for i := range bp.Pages {
|
||
p := &bp.Pages[i]
|
||
if !strings.EqualFold(strings.TrimSpace(p.Type), "list") {
|
||
continue
|
||
}
|
||
if p.Layout == nil {
|
||
p.Layout = &PageLayout{}
|
||
}
|
||
p.Layout.Actions = ensureListActions(p.Layout.Actions)
|
||
if p.Layout.ActionLabels == nil {
|
||
p.Layout.ActionLabels = map[string]string{}
|
||
}
|
||
if _, ok := p.Layout.ActionLabels["import"]; !ok {
|
||
p.Layout.ActionLabels["import"] = "导入 Excel"
|
||
}
|
||
if _, ok := p.Layout.ActionLabels["export"]; !ok {
|
||
p.Layout.ActionLabels["export"] = "导出 Excel"
|
||
}
|
||
}
|
||
}
|
||
|
||
func disableDefaultImport(bp *Blueprint) bool {
|
||
if len(bp.Meta.UI) == 0 {
|
||
return false
|
||
}
|
||
var ui map[string]any
|
||
if err := json.Unmarshal(bp.Meta.UI, &ui); err != nil {
|
||
return false
|
||
}
|
||
v, ok := ui["disable_default_import"]
|
||
if !ok {
|
||
return false
|
||
}
|
||
switch t := v.(type) {
|
||
case bool:
|
||
return t
|
||
case string:
|
||
return strings.EqualFold(t, "true") || t == "1"
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func resourceReadOnly(ops []string) bool {
|
||
if len(ops) == 0 {
|
||
return false
|
||
}
|
||
writable := false
|
||
for _, op := range ops {
|
||
switch strings.ToLower(strings.TrimSpace(op)) {
|
||
case "create", "update", "delete", "import", "export":
|
||
writable = true
|
||
}
|
||
}
|
||
// 仅 list/get → 只读,不强加 import
|
||
if !writable {
|
||
onlyRead := true
|
||
for _, op := range ops {
|
||
switch strings.ToLower(strings.TrimSpace(op)) {
|
||
case "list", "get", "":
|
||
default:
|
||
onlyRead = false
|
||
}
|
||
}
|
||
return onlyRead
|
||
}
|
||
return false
|
||
}
|
||
|
||
func ensureOpList(ops []string, add ...string) []string {
|
||
seen := map[string]struct{}{}
|
||
out := make([]string, 0, len(ops)+len(add))
|
||
for _, op := range ops {
|
||
op = strings.ToLower(strings.TrimSpace(op))
|
||
if op == "" {
|
||
continue
|
||
}
|
||
if _, ok := seen[op]; ok {
|
||
continue
|
||
}
|
||
seen[op] = struct{}{}
|
||
out = append(out, op)
|
||
}
|
||
for _, op := range add {
|
||
op = strings.ToLower(strings.TrimSpace(op))
|
||
if op == "" {
|
||
continue
|
||
}
|
||
if _, ok := seen[op]; ok {
|
||
continue
|
||
}
|
||
seen[op] = struct{}{}
|
||
out = append(out, op)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func ensureListActions(actions []string) []string {
|
||
if len(actions) == 0 {
|
||
return append([]string{}, defaultListActions...)
|
||
}
|
||
seen := map[string]struct{}{}
|
||
out := make([]string, 0, len(actions)+2)
|
||
for _, a := range actions {
|
||
a = strings.ToLower(strings.TrimSpace(a))
|
||
if a == "" {
|
||
continue
|
||
}
|
||
if _, ok := seen[a]; ok {
|
||
continue
|
||
}
|
||
seen[a] = struct{}{}
|
||
out = append(out, a)
|
||
}
|
||
for _, need := range []string{"import", "export"} {
|
||
if _, ok := seen[need]; ok {
|
||
continue
|
||
}
|
||
// 插在 refresh 前;若无 refresh 则追加
|
||
inserted := false
|
||
for i, a := range out {
|
||
if a == "refresh" {
|
||
out = append(out[:i], append([]string{need}, out[i:]...)...)
|
||
seen[need] = struct{}{}
|
||
inserted = true
|
||
break
|
||
}
|
||
}
|
||
if !inserted {
|
||
out = append(out, need)
|
||
seen[need] = struct{}{}
|
||
}
|
||
}
|
||
return out
|
||
}
|