diff --git a/app/models/kind.json b/app/models/kind.json new file mode 100644 index 00000000..53f53de9 --- /dev/null +++ b/app/models/kind.json @@ -0,0 +1,589 @@ +{ + "name": "服务类型", + "table": { + "name": "kind", + "comment": "服务类型", + "engine": "InnoDB" + }, + "columns": [ + { "name": "id", "type": "ID" }, + { + "name": "category_id", + "type": "integer", + "length": 128, + "comment": "gw_category ID(废弃)", + "nullable": true, + "index": true + }, + { + "name": "type_id", + "type": "integer", + "length": 128, + "comment": "gw_type ID(废弃)", + "nullable": true, + "index": true + }, + { + "name": "name", + "type": "string", + "comment": "名称", + "index": true + }, + { + "name": "parent", + "type": "bigInteger", + "length": 128, + "comment": "父类型ID", + "nullable": false, + "default": 0, + "index": true + }, + { + "name": "status", + "type": "enum", + "option": ["enabled", "disabled"], + "comment": "状态 enabled 开启, disabled 关闭", + "default": "enabled", + "index": true + }, + { + "name": "rank", + "type": "bigInteger", + "length": 128, + "comment": "排序", + "nullable": false, + "default": 9999, + "index": true + } + ], + "relations": { + "parent": { + "type": "hasOne", + "key": "parent", + "model": "Kind", + "foreign": "id" + }, + "children": { + "type": "hasMany", + "key": "id", + "model": "Kind", + "foreign": "parent" + }, + "nodes": { + "type": "treeNodes", + "key": "id", + "model": "Kind", + "parent": "parent", + "max_level": 256 + } + }, + "option": { "timestamps": true, "soft_deletes": true }, + "values": [ + { + "id": 1, + "name": "云服务", + "category_id": null, + "type_id": null, + "rank": 1, + "parent": 0 + }, + + { + "id": 2, + "name": "基础服务", + "category_id": null, + "type_id": null, + "rank": 1, + "parent": 1 + }, + { + "id": 3, + "name": "云主机", + "category_id": null, + "type_id": 4, + "rank": 1, + "parent": 2 + }, + { + "id": 4, + "name": "对象存储", + "category_id": null, + "type_id": 5, + "rank": 2, + "parent": 2 + }, + { + "id": 5, + "name": "云数据库", + "category_id": null, + "type_id": 6, + "rank": 3, + "parent": 2 + }, + { + "id": 6, + "name": "块存储", + "category_id": null, + "type_id": 7, + "rank": 4, + "parent": 2 + }, + { + "id": 7, + "name": "应用托管容器", + "category_id": null, + "type_id": 8, + "rank": 5, + "parent": 2 + }, + { + "id": 8, + "name": "云缓存", + "category_id": null, + "type_id": 9, + "rank": 6, + "parent": 2 + }, + { + "id": 9, + "name": "本地负载均衡", + "category_id": null, + "type_id": 10, + "rank": 7, + "parent": 2 + }, + { + "id": 10, + "name": "全局负载均衡", + "category_id": null, + "type_id": 13, + "rank": 8, + "parent": 2 + }, + { + "id": 11, + "name": "云分发", + "category_id": null, + "type_id": 11, + "rank": 9, + "parent": 2 + }, + { + "id": 12, + "name": "企业级SaaS", + "category_id": null, + "type_id": 12, + "rank": 10, + "parent": 2 + }, + { + "id": 13, + "name": "云桌面", + "category_id": null, + "type_id": 14, + "rank": 11, + "parent": 2 + }, + { + "id": 14, + "name": "云备份", + "category_id": null, + "type_id": 17, + "rank": 12, + "parent": 2 + }, + { + "id": 15, + "name": "GPU云主机", + "category_id": null, + "type_id": 18, + "rank": 13, + "parent": 2 + }, + { + "id": 16, + "name": "物理云主机", + "category_id": null, + "type_id": 20, + "rank": 14, + "parent": 2 + }, + { + "id": 17, + "name": "智能云", + "category_id": 41, + "type_id": null, + "rank": 2, + "parent": 2 + }, + + { + "id": 18, + "name": "软件和开发", + "category_id": null, + "type_id": null, + "rank": 2, + "parent": 0 + }, + { + "id": 19, + "name": "虚拟化及管理", + "category_id": 59, + "type_id": null, + "rank": 1, + "parent": 18 + }, + { + "id": 20, + "name": "容器解决方案", + "category_id": 65, + "type_id": null, + "rank": 2, + "parent": 18 + }, + { + "id": 21, + "name": "微服务解决方案", + "category_id": 76, + "type_id": null, + "rank": 3, + "parent": 18 + }, + { + "id": 22, + "name": "serverless解决方案", + "category_id": 82, + "type_id": null, + "rank": 4, + "parent": 18 + }, + { + "id": 23, + "name": "云管理和云运营", + "category_id": null, + "type_id": null, + "rank": 3, + "parent": 0 + }, + { + "id": 24, + "name": "混合云", + "category_id": null, + "type_id": null, + "rank": 1, + "parent": 23 + }, + { + "id": 25, + "name": "混合云解决方案", + "category_id": 88, + "type_id": null, + "rank": 1, + "parent": 24 + }, + { + "id": 26, + "name": "混合云安全", + "category_id": 671, + "type_id": null, + "rank": 2, + "parent": 24 + }, + { + "id": 27, + "name": "多云管理", + "category_id": 94, + "type_id": null, + "rank": 2, + "parent": 23 + }, + { + "id": 28, + "name": "金牌运维", + "category_id": 120, + "type_id": null, + "rank": 3, + "parent": 23 + }, + { + "id": 29, + "name": "研发运营一体化", + "category_id": 443, + "type_id": null, + "rank": 4, + "parent": 23 + }, + { + "id": 30, + "name": "MSP", + "category_id": 112, + "type_id": null, + "rank": 5, + "parent": 23 + }, + { + "id": 31, + "name": "安全与保险", + "category_id": null, + "type_id": null, + "rank": 4, + "parent": 0 + }, + { + "id": 32, + "name": "风险管理", + "category_id": 141, + "type_id": null, + "rank": 1, + "parent": 31 + }, + { + "id": 33, + "name": "云服务用户数据保护", + "category_id": 152, + "type_id": null, + "rank": 2, + "parent": 31 + }, + { + "id": 34, + "name": "业务风控", + "category_id": null, + "type_id": null, + "rank": 3, + "parent": 31 + }, + { + "id": 35, + "name": "内容安全", + "category_id": 159, + "type_id": null, + "rank": 1, + "parent": 34 + }, + { + "id": 36, + "name": "反交易欺诈", + "category_id": 164, + "type_id": null, + "rank": 2, + "parent": 34 + }, + { + "id": 37, + "name": "反信贷欺诈", + "category_id": 165, + "type_id": null, + "rank": 3, + "parent": 34 + }, + { + "id": 38, + "name": "反营销欺诈", + "category_id": 166, + "type_id": null, + "rank": 4, + "parent": 34 + }, + { + "id": 39, + "name": "反钓鱼欺诈", + "category_id": 167, + "type_id": null, + "rank": 5, + "parent": 34 + }, + { + "id": 40, + "name": "云主机安全", + "category_id": 184, + "type_id": null, + "rank": 4, + "parent": 31 + }, + { + "id": 41, + "name": "态势感知", + "category_id": 190, + "type_id": null, + "rank": 5, + "parent": 31 + }, + { + "id": 42, + "name": "云保险", + "category_id": 272, + "type_id": null, + "rank": 6, + "parent": 31 + }, + { + "id": 43, + "name": "云网&云边", + "category_id": null, + "type_id": null, + "rank": 5, + "parent": 0 + }, + { + "id": 44, + "name": "云平台网络能力", + "category_id": 102, + "type_id": null, + "rank": 1, + "parent": 43 + }, + { + "id": 45, + "name": "SD-WAN", + "category_id": 106, + "type_id": null, + "rank": 2, + "parent": 43 + }, + { + "id": 46, + "name": "物联网", + "category_id": 234, + "type_id": null, + "rank": 3, + "parent": 43 + }, + { + "id": 47, + "name": "行业云", + "category_id": null, + "type_id": null, + "rank": 6, + "parent": 0 + }, + { + "id": 48, + "name": "政务", + "category_id": null, + "type_id": null, + "rank": 1, + "parent": 47 + }, + { + "id": 49, + "name": "政务云综合水平评估", + "category_id": 215, + "type_id": null, + "rank": 1, + "parent": 48 + }, + { + "id": 50, + "name": "可信政务云评估", + "category_id": 216, + "type_id": null, + "rank": 2, + "parent": 48 + }, + { + "id": 51, + "name": "金融", + "category_id": 225, + "type_id": null, + "rank": 2, + "parent": 47 + }, + { + "id": 52, + "name": "开源治理", + "category_id": null, + "type_id": null, + "rank": 7, + "parent": 0 + }, + { + "id": 53, + "name": "面向开源用户企业", + "category_id": 196, + "type_id": null, + "rank": 1, + "parent": 52 + }, + { + "id": 54, + "name": "面向自发开源企业", + "category_id": 202, + "type_id": null, + "rank": 2, + "parent": 52 + }, + { + "id": 55, + "name": "开源项目评估", + "category_id": 711, + "type_id": null, + "rank": 3, + "parent": 52 + }, + { + "id": 56, + "name": "开源工具评估", + "category_id": 720, + "type_id": null, + "rank": 4, + "parent": 52 + }, + { + "id": 57, + "name": "检测平台", + "category_id": null, + "type_id": null, + "rank": 8, + "parent": 0 + }, + { + "id": 58, + "name": "云主机分级", + "category_id": 371, + "type_id": null, + "rank": 1, + "parent": 57 + }, + { + "id": 59, + "name": "监管支撑", + "category_id": null, + "type_id": null, + "rank": 9, + "parent": 0 + }, + { + "id": 60, + "name": "企业上云效果成熟度", + "category_id": 250, + "type_id": null, + "rank": 1, + "parent": 59 + }, + { + "id": 61, + "name": "综合信用评估", + "category_id": null, + "type_id": null, + "rank": 2, + "parent": 59 + }, + { + "id": 62, + "name": "云服务企业", + "category_id": 261, + "type_id": null, + "rank": 1, + "parent": 61 + }, + { + "id": 63, + "name": "CDN服务企业", + "category_id": 271, + "type_id": null, + "rank": 2, + "parent": 61 + } + ] +} diff --git a/app/models/service.json b/app/models/service.json new file mode 100644 index 00000000..9b76a95c --- /dev/null +++ b/app/models/service.json @@ -0,0 +1,122 @@ +{ + "name": "云服务库", + "table": { + "name": "service", + "comment": "云服务表", + "engine": "InnoDB" + }, + "columns": [ + { "name": "id", "type": "ID" }, + { + "name": "short_name", + "type": "string", + "length": 200, + "comment": "简称", + "index": true + }, + { + "name": "name", + "type": "string", + "length": 200, + "comment": "服务名称", + "index": true + }, + { + "name": "kind", + "type": "string", + "length": 200, + "comment": "服务类型", + "index": true + }, + { + "name": "manu", + "type": "string", + "length": 200, + "comment": "所属厂商", + "index": true + }, + { + "name": "registed_at", + "type": "string", + "length": 200, + "comment": "注册地", + "nullable": true, + "index": true + }, + { + "name": "link", + "type": "string", + "length": 200, + "comment": "官网地址", + "nullable": true + }, + { + "name": "contact_name", + "type": "string", + "length": 200, + "comment": "联系人", + "nullable": true, + "index": true + }, + { + "name": "contact_mobile", + "type": "string", + "comment": "联系电话", + "nullable": true, + "index": true + }, + { + "name": "fields", + "type": "string", + "length": 500, + "comment": "JSON|服务领域(多选)", + "nullable": true, + "index": true + }, + { + "name": "price_options", + "type": "string", + "length": 500, + "default": "[\"按月订阅\"]", + "comment": "JSON|订阅方式(多选)", + "index": true + }, + { + "name": "industries", + "type": "string", + "length": 500, + "comment": "JSON|行业覆盖(多选)", + "nullable": true, + "index": true + }, + { + "name": "rank", + "type": "integer", + "default": 9999999, + "comment": "查询排序" + }, + { + "name": "status", + "type": "enum", + "default": "enabled", + "option": ["enabled", "disabled"], + "comment": "状态 enabled 开启, disabled 关闭", + "index": true + } + ], + "relations": { + "manu": { + "type": "hasOne", + "key": "manu", + "model": "Manu", + "foreign": "id" + }, + "kind": { + "type": "hasOne", + "key": "kind", + "model": "Kind", + "foreign": "id" + } + }, + "option": { "timestamps": true, "soft_deletes": true } +} diff --git a/app/tables/services.json b/app/tables/service.json similarity index 77% rename from app/tables/services.json rename to app/tables/service.json index 3d936fcb..0f90dc12 100644 --- a/app/tables/services.json +++ b/app/tables/service.json @@ -1,86 +1,64 @@ { "name": "云服务库", + "version": "1.0.0", "decription": "可信云云服务库", - "apis": { - "bind": { - "model": "service", - "withs": { - "manu": { - "query": { - "select": ["id", "name", "short_name", "status"] - } - }, - "kind": { - "query": { - "select": ["id", "name"] - } + "bind": { + "model": "service", + "withs": { + "manu": { + "query": { + "select": ["id", "name", "short_name", "status"] + } + }, + "kind": { + "query": { + "select": ["id", "name"] } } - }, + } + }, + "apis": { "search": { "process": "models.service.Paginate", - "in": [":query-param", "$query.page", "$query.pagesize"], - "default": { - "withs": { - "manu": { - "query": { - "select": ["id", "name", "short_name", "status"] + "default": [ + { + "withs": { + "manu": { + "query": { + "select": ["id", "name", "short_name", "status"] + } + }, + "kind": { + "query": { + "select": ["id", "name"] + } } }, - "kind": { - "query": { - "select": ["id", "name"] - } - } + "wheres": [{ "column": "status", "value": "enabled" }] }, - "wheres": [{ "column": "status", "value": "enabled" }], - "pagesize": 15 - } + null, + 15 + ] }, "find": { "process": "models.service.Find", - "in": ["$param.id"], - "default": { - "withs": { - "manu": { - "query": { - "select": ["id", "name", "short_name", "status"] - } - }, - "kind": { - "query": { - "select": ["id", "name"] + "default": [ + null, + { + "withs": { + "manu": { + "query": { + "select": ["id", "name", "short_name", "status"] + } + }, + "kind": { + "query": { + "select": ["id", "name"] + } } } } - } - }, - "save": { - "process": "models.service.Find", - "in": [":payload"], - "default": { - "fields": ["视频会议"] - } - }, - "delete": { - "process": "models.service.Delete", - "in": ["$param.id"] - }, - "insert": { - "process": "models.service.Insert", - "in": ["$payload.columns", "$payload.data"] - }, - "deletewhere": { - "process": "models.service.DeleteWhere", - "in": [":query-param"] - }, - "updatewhere": { - "process": "models.service.UpdateWhere", - "in": [":query-param", ":payload"] - }, - "import": { - "process": "models.service.Import", - "in": ["$file.file", "$param.kind", ":headers"] + ] } }, "columns": { @@ -295,24 +273,28 @@ "关键词": { "label": "关键词", "bind": "where.name.like", - "type": "input", - "props": { - "placeholder": "请输入关键词" + "input": { + "type": "input", + "props": { + "placeholder": "请输入关键词" + } } }, "所属厂商": { "label": "厂商", "bind": "where.manu_id.in", - "type": "select", - "props": { - "searchable": true, - "multiple": true, - "placeholder": "请选择所属厂商", - "remote": { - "api": "/api/manu/get", - "query": { - "select": ["id", "name"], - "keyword": "where_name_like" + "input": { + "type": "select", + "props": { + "searchable": true, + "multiple": true, + "placeholder": "请选择所属厂商", + "remote": { + "api": "/api/manu/get", + "query": { + "select": ["id", "name"], + "keyword": "where_name_like" + } } } } @@ -320,17 +302,19 @@ "服务类型": { "label": "类型", "bind": "where.kind_id.in", - "type": "select", - "props": { - "tree": true, - "searchable": true, - "multiple": true, - "placeholder": "请选择所属类型", - "remote": { - "api": "/api/kind/tree", - "query": { - "select": ["id", "name"], - "keyword": "where_name_like" + "input": { + "type": "select", + "props": { + "tree": true, + "searchable": true, + "multiple": true, + "placeholder": "请选择所属类型", + "remote": { + "api": "/api/kind/tree", + "query": { + "select": ["id", "name"], + "keyword": "where_name_like" + } } } } @@ -373,7 +357,7 @@ "deleteWhere": {}, "updateSelect": {}, "deleteSelect": {}, - "pagination": ["pages", "prev", "next"], + "pagination": {}, "setting": {} } }, diff --git a/global/vars.go b/global/vars.go index 57d82cd5..51974f40 100644 --- a/global/vars.go +++ b/global/vars.go @@ -46,7 +46,9 @@ func init() { Conf = NewConfig() // 数据库连接 - capsule.AddConn("primary", "mysql", Conf.Database.Primary[0]).SetAsGlobal() + if len(Conf.Database.Primary) > 0 { + capsule.AddConn("primary", "mysql", Conf.Database.Primary[0]).SetAsGlobal() + } // 加密密钥 gou.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, Conf.Database.AESKey), "AES") diff --git a/table/api.go b/table/api.go new file mode 100644 index 00000000..07b6ddc5 --- /dev/null +++ b/table/api.go @@ -0,0 +1,5 @@ +package table + +// loadAPIs 加载数据管理 API +func (tab *Table) loadAPIs() { +} diff --git a/table/columns.go b/table/columns.go new file mode 100644 index 00000000..88a3a4ff --- /dev/null +++ b/table/columns.go @@ -0,0 +1,113 @@ +package table + +import ( + "fmt" + + "github.com/yaoapp/gou" +) + +var elms = map[string]Column{ + "string": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "char": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "text": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "mediumText": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "longText": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "binary": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "date": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "datetime": {View: Render{Type: "label"}, Edit: Render{Type: "datetime"}}, + "datetimeTz": {View: Render{Type: "label"}, Edit: Render{Type: "datetime"}}, + "time": {View: Render{Type: "label"}, Edit: Render{Type: "time"}}, + "timeTz": {View: Render{Type: "label"}, Edit: Render{Type: "time"}}, + "timestamp": {View: Render{Type: "label"}, Edit: Render{Type: "datetime"}}, + "timestampTz": {View: Render{Type: "label"}, Edit: Render{Type: "datetime"}}, + "tinyInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "tinyIncrements": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedTinyInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "smallInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "smallIncrements": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedSmallInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "integer": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "increments": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "bigInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "bigIncrements": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedBigInteger": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "id": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "ID": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "decimal": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedDecimal": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "float": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedFloat": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "double": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "unsignedDouble": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "boolean": {View: Render{Type: "label"}, Edit: Render{Type: "checkbox"}}, + "enum": {View: Render{Type: "label"}, Edit: Render{Type: "select"}}, + "json": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "JSON": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "jsonb": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "JSONB": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "uuid": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "ipAddress": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "macAddress": {View: Render{Type: "label"}, Edit: Render{Type: "input"}}, + "year": {View: Render{Type: "label"}, Edit: Render{Type: "datetime"}}, +} + +// loadColumns 加载字段呈现方式 +func (table *Table) loadColumns() { + if table.Bind.Model == "" { + return + } + defaults := getDefaultColumns(table.Bind.Model) + for name, column := range table.Columns { + defaults[name] = column + } + table.Columns = defaults +} + +// getDefaultColumns 读取数据模型字段的呈现方式 +func getDefaultColumns(name string) map[string]Column { + mod := gou.Select(name) + cmap := mod.Columns + columns := map[string]Column{} + + for name, col := range cmap { + vcol, has := elms[col.Type] + if !has { + continue + } + + label := col.Label + if label == "" { + label = col.Comment + } + if label == "" { + label = name + } + + vcol.Label = label + if vcol.View.Props == nil { + vcol.View.Props = map[string]interface{}{} + } + if vcol.Edit.Props == nil { + vcol.Edit.Props = map[string]interface{}{} + } + vcol.View.Props["value"] = fmt.Sprintf(":%s", col.Name) + vcol.Edit.Props["value"] = fmt.Sprintf(":%s", col.Name) + + // 枚举型 + if col.Type == "enum" { + options := []map[string]string{} + for _, opt := range col.Option { + options = append(options, map[string]string{ + "label": opt, + "value": opt, + }) + } + vcol.Edit.Props["options"] = options + } + + columns[name] = vcol + columns[label] = vcol + } + return columns +} diff --git a/table/filters.go b/table/filters.go new file mode 100644 index 00000000..655bfa99 --- /dev/null +++ b/table/filters.go @@ -0,0 +1,77 @@ +package table + +import ( + "fmt" + + "github.com/yaoapp/gou" +) + +// loadFilters 加载查询过滤器 +func (table *Table) loadFilters() { + if table.Bind.Model == "" { + return + } + defaults := getDefaultFilters(table.Bind.Model) + for name, filter := range table.Filters { + defaults[name] = filter + } + table.Filters = defaults +} + +// getDefaultFilters 读取数据模型索引字段的过滤器 +func getDefaultFilters(name string) map[string]Filter { + + mod := gou.Select(name) + cmap := mod.Columns + filters := map[string]Filter{} + for _, index := range mod.MetaData.Indexes { + for _, col := range index.Columns { + if _, has := cmap[col]; !has { + continue + } + // primary,unique,index,match + switch index.Type { + case "index", "match": + cmap[col].Index = true + break + case "unique": + cmap[col].Unique = true + break + case "primary": + cmap[col].Primary = true + break + } + } + } + + for name, col := range cmap { + + if col.Type != "ID" && !col.Index && !col.Unique && !col.Primary { + continue + } + + vcol, has := elms[col.Type] + if !has { + continue + } + + label := col.Label + if label == "" { + label = col.Comment + } + if label == "" { + label = name + } + + filter := Filter{ + Label: label, + Bind: fmt.Sprintf("where.%s.eq", name), + Input: vcol.Edit, + } + filters[name] = filter + filters[label] = filter + } + + return filters + +} diff --git a/table/table.go b/table/table.go new file mode 100644 index 00000000..2d68b5a4 --- /dev/null +++ b/table/table.go @@ -0,0 +1,64 @@ +package table + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/yaoapp/gou/helper" + "github.com/yaoapp/kun/exception" +) + +// Tables 已载入模型 +var Tables = map[string]*Table{} + +// Load 载入数据表格 +func Load(source string, name string) *Table { + var input io.Reader = nil + if strings.HasPrefix(source, "file://") || strings.HasPrefix(source, "fs://") { + filename := strings.TrimPrefix(source, "file://") + filename = strings.TrimPrefix(filename, "fs://") + file, err := os.Open(filename) + if err != nil { + exception.Err(err, 400).Throw() + } + defer file.Close() + input = file + } else { + input = strings.NewReader(source) + } + + table := Table{ + Source: source, + Table: name, + } + err := helper.UnmarshalFile(input, &table) + if err != nil { + exception.Err(err, 400).Throw() + } + + table.loadColumns() + table.loadFilters() + table.loadAPIs() + Tables[name] = &table + return Tables[name] +} + +// Select 读取已加载表格配置 +func Select(name string) *Table { + tab, has := Tables[name] + if !has { + exception.New( + fmt.Sprintf("Table:%s; 尚未加载", name), + 400, + ).Throw() + } + return tab +} + +// Reload 更新数据表格配置 +func (tab *Table) Reload() *Table { + tab = Load(tab.Source, tab.Name) + return tab +} diff --git a/table/table_test.go b/table/table_test.go new file mode 100644 index 00000000..eb67cded --- /dev/null +++ b/table/table_test.go @@ -0,0 +1,38 @@ +package table + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/xiang/global" +) + +func TestLoad(t *testing.T) { + global.Load(global.Conf) + root := "fs://" + path.Join(global.Conf.Source, "/app/tables/service.json") + Load(root, "service").Reload() + + table, has := Tables["service"] + assert.Equal(t, table.Table, "service") + assert.Equal(t, table.Source, root) + assert.True(t, has) + + _, has = table.Columns["id"] + assert.True(t, has) + + price, has := table.Columns["计费方式"] + assert.True(t, has) + if has { + assert.True(t, price.Edit.Props["multiple"].(bool)) + } + + _, has = table.Filters["id"] + assert.True(t, has) + + keywords, has := table.Filters["关键词"] + assert.True(t, has) + if has { + assert.True(t, keywords.Bind == "where.name.like") + } +} diff --git a/table/types.go b/table/types.go new file mode 100644 index 00000000..55515f3c --- /dev/null +++ b/table/types.go @@ -0,0 +1,80 @@ +package table + +import "github.com/yaoapp/gou" + +// Table 数据表格配置结构 +type Table struct { + Table string `json:"-"` + Source string `json:"-"` + Name string `json:"name"` + Version string `json:"version"` + Bind Bind `json:"bind,omitempty"` + APIs APIs `json:"apis,omitempty"` + Columns map[string]Column `json:"columns,omitempty"` + Filters map[string]Filter `json:"filters,omitempty"` + List Page `json:"list,omitempty"` + Edit Page `json:"edit,omitempty"` + View Page `json:"view,omitempty"` + Insert Page `json:"insert,omitempty"` +} + +// Bind 绑定数据模型 +type Bind struct { + Model string `json:"model"` + Withs map[string]gou.With `json:"withs,omitempty"` +} + +// APIs API 配置数据结构 +type APIs struct { + Search API `json:"search,omitempty"` + Find API `json:"find,omitempty"` + Save API `json:"save,omitempty"` + Delete API `json:"delete,omitempty"` + Insert API `json:"insert,omitempty"` + DeleteWhere API `json:"delete-where,omitempty"` + DeleteIn API `json:"delete-in,omitempty"` + UpdateWhere API `json:"update-where,omitempty"` + UpdateIn API `json:"update-in,omitempty"` + ImportUpload API `json:"import-upload,omitempty"` + ImportPreview API `json:"import-preview,omitempty"` + ImportSync API `json:"import-sync,omitempty"` + ImportAsync API `json:"import-async,omitempty"` + ImportAsyncTasks API `json:"import-async-tasks,omitempty"` + ImportAsyncTasksID API `json:"import-async-tasks-id,omitempty"` + Setting API `json:"setting,omitempty"` +} + +// API API 配置数据结构 +type API struct { + Process string `json:"process,omitempty"` + Guard string `json:"guard,omitempty"` + Default []interface{} `json:"default,omitempty"` +} + +// Column 字段呈现方式 +type Column struct { + Label string `json:"label"` + View Render `json:"view,omitempty"` + Edit Render `json:"edit,omitempty"` + Form Render `json:"form,omitempty"` +} + +// Filter 查询过滤器 +type Filter struct { + Label string `json:"label"` + Bind string `json:"bind,omitempty"` + Input Render `json:"input,omitempty"` +} + +// Page 页面 +type Page struct { + Primary string `json:"primary"` + Layout map[string]interface{} `json:"layout"` + Actions map[string]Render `json:"actions,omitempty"` +} + +// Render 组件渲染方式 +type Render struct { + Type string `json:"type,omitempty"` + Props map[string]interface{} `json:"props,omitempty"` +}