feat(config): 扩展数据类型映射与数据库字段备注提取功能
- 在 ColumnDataType 中新增对 PostgreSQL 和 Oracle 数据类型的支持 - 更新 Db2JSON 方法,优化对达梦数据库表和列信息的查询逻辑 - 增强字段备注解析,支持从备注中提取提示信息并清理标签 - 更新文档,详细说明字段备注格式及前端提示的展示效果
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: 修复DM列注释读取
|
||||
overview: 修复达梦数据库列注释(COMMENT)未被正确读取的问题,导致管理端所有字段显示为数据库字段名而非中文备注。
|
||||
todos:
|
||||
- id: fix-dm-col-comments
|
||||
content: 修改 makecode.go 列信息查询,列注释改用 USER_COL_COMMENTS 替代 ALL_COL_COMMENTS
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 修复达梦数据库列注释读取失败
|
||||
|
||||
## 问题分析
|
||||
|
||||
通过 MCP 查询 `adminDB.json` 确认:所有表的字段 label 全部等于字段名本身(如 `"id"`, `"name"`, `"phone"`),没有中文备注。但表级 label 正常(如"消息主表"、"用户表")。
|
||||
|
||||
用户确认数据库字段确实有备注。
|
||||
|
||||
### 根因
|
||||
|
||||
[makecode.go](d:\work\hotimev1.5\code\makecode.go) 第 170 行当前的列查询 SQL:
|
||||
|
||||
```sql
|
||||
FROM ALL_TAB_COLUMNS c
|
||||
LEFT JOIN ALL_COL_COMMENTS m
|
||||
ON c.TABLE_NAME=m.TABLE_NAME AND c.COLUMN_NAME=m.COLUMN_NAME AND c.OWNER=m.OWNER
|
||||
WHERE c.TABLE_NAME='...' AND c.OWNER='ZWBG'
|
||||
```
|
||||
|
||||
`LEFT JOIN` 条件中 `c.OWNER=m.OWNER` 要求 `ALL_COL_COMMENTS` 的 `OWNER` 也等于 `'ZWBG'`。但在 DM 中,`ALL_COL_COMMENTS` 的 `OWNER` 值可能与 `ALL_TAB_COLUMNS` 不一致(DM 的 Oracle 兼容视图在 OWNER 语义上可能存在差异),导致 JOIN 匹配不上,`m.COMMENTS` 全部为 NULL。
|
||||
|
||||
对比:表注释用的 `ALL_TAB_COMMENTS WHERE OWNER='ZWBG'` 能正确返回中文备注,说明表级视图的 OWNER 一致,但列级视图可能不同。
|
||||
|
||||
## 修复方案
|
||||
|
||||
修改 [makecode.go](d:\work\hotimev1.5\code\makecode.go) 第 169-171 行,列注释改用 `USER_COL_COMMENTS`(自动使用当前 schema 上下文 ZWBG,无需 OWNER 过滤),列元数据继续用 `ALL_TAB_COLUMNS`(保留 OWNER 过滤防止系统表):
|
||||
|
||||
```go
|
||||
if db.Type == "dm" || db.Type == "dameng" {
|
||||
tableInfo = db.Query(`SELECT c.COLUMN_NAME AS "name", c.DATA_TYPE AS "type", m.COMMENTS AS "label", c.NULLABLE AS "must", c.DATA_DEFAULT AS "dflt_value" FROM ALL_TAB_COLUMNS c LEFT JOIN USER_COL_COMMENTS m ON c.TABLE_NAME=m.TABLE_NAME AND c.COLUMN_NAME=m.COLUMN_NAME WHERE c.TABLE_NAME='` + v.GetString("name") + `' AND c.OWNER='` + strings.ToUpper(db.DBName) + `' ORDER BY c.COLUMN_ID`)
|
||||
}
|
||||
```
|
||||
|
||||
关键变化:
|
||||
|
||||
- `ALL_COL_COMMENTS` 改为 `USER_COL_COMMENTS`
|
||||
- 去掉 JOIN 中的 `AND c.OWNER=m.OWNER`(`USER_COL_COMMENTS` 没有 OWNER 列)
|
||||
- WHERE 条件 `c.OWNER='ZWBG'` 保留(作用于 `ALL_TAB_COLUMNS`,防止系统表列混入)
|
||||
|
||||
原理:框架连接 DM 用 `?schema=ZWBG`,`USER_COL_COMMENTS` 自动返回当前 schema(ZWBG)的列注释,绕开了 OWNER 不一致问题。
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: 修复DM类型映射大小写
|
||||
overview: DM 返回的列类型为大写(BIGINT、VARCHAR、INT、TIMESTAMP),但 ColumnDataType 的匹配键是小写且 strings.Contains 区分大小写,导致类型映射全部失败。
|
||||
todos:
|
||||
- id: fix-type-case
|
||||
content: 修改 makecode.go 类型比较加 strings.ToLower 忽略大小写
|
||||
status: completed
|
||||
- id: add-dm-types
|
||||
content: 在 config.go 的 ColumnDataType 中追加 DM 特有数据类型映射
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 修复达梦数据库列类型映射失败
|
||||
|
||||
## 问题分析
|
||||
|
||||
通过 MCP 查询 `adminDB.json` 确认:大量字段 type 仍为 DM 原始类型(`BIGINT`、`VARCHAR`、`INT`、`TIMESTAMP`、`DECIMAL`),未映射到框架类型(`number`、`text`、`time`),导致前端编辑页无法识别控件类型。
|
||||
|
||||
### 根因
|
||||
|
||||
[config.go](d:\work\hotimev1.5\code\config.go) 第 43-59 行定义的 `ColumnDataType` 映射表键全部为小写:
|
||||
|
||||
```go
|
||||
var ColumnDataType = map[string]string{
|
||||
"int": "number",
|
||||
"char": "text",
|
||||
"text": "text",
|
||||
"time": "time",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
[makecode.go](d:\work\hotimev1.5\code\makecode.go) 第 191-194 行的匹配逻辑使用 `strings.Contains`,**区分大小写**:
|
||||
|
||||
```go
|
||||
for k, v1 := range ColumnDataType {
|
||||
if strings.Contains(info.GetString("type"), k) { // "BIGINT" 不包含 "int"
|
||||
info["type"] = v1
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
DM 返回的类型全部为大写(`INT`、`VARCHAR`、`BIGINT`),而键为小写(`int`、`char`),`strings.Contains("BIGINT", "int")` 返回 `false`,映射全部跳过。
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 修改 1:类型比较忽略大小写 - [makecode.go](d:\work\hotimev1.5\code\makecode.go)
|
||||
|
||||
第 192 行,将 `info.GetString("type")` 转为小写再匹配:
|
||||
|
||||
```go
|
||||
if strings.Contains(strings.ToLower(info.GetString("type")), k) {
|
||||
```
|
||||
|
||||
效果验证:
|
||||
|
||||
- `"BIGINT"` -> `"bigint"` -> contains `"int"` -> `"number"`
|
||||
- `"VARCHAR"` -> `"varchar"` -> contains `"char"` -> `"text"`
|
||||
- `"INT"` -> `"int"` -> contains `"int"` -> `"number"`
|
||||
- `"TIMESTAMP"` -> `"timestamp"` -> contains `"time"` -> `"time"`
|
||||
- `"DECIMAL"` -> `"decimal"` -> contains `"decimal"` -> `"number"`
|
||||
|
||||
### 修改 2:补充 DM 特有类型 - [config.go](d:\work\hotimev1.5\code\config.go)
|
||||
|
||||
在 `ColumnDataType` 中追加 DM 特有的数据类型,确保 CLOB、NUMBER 等也能被识别:
|
||||
|
||||
```go
|
||||
var ColumnDataType = map[string]string{
|
||||
// 现有 MySQL/SQLite 类型...
|
||||
// DM 特有类型
|
||||
"clob": "text",
|
||||
"number": "number",
|
||||
"numeric": "number",
|
||||
"binary": "text",
|
||||
"boolean": "number",
|
||||
"bit": "number",
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: 修复Table和Edit组件
|
||||
overview: 修复 Table.vue 的重复请求和搜索防抖问题,以及 Edit.vue 密码改为弹窗独立修改。
|
||||
todos:
|
||||
- id: fix-double-request
|
||||
content: "Table.vue: 添加 _skipFormWatch 标志,修复 mounted 和 watcher 导致的重复请求"
|
||||
status: completed
|
||||
- id: fix-keyword-search
|
||||
content: "Table.vue: 关键字搜索改为点击按钮/Enter触发,不再实时触发"
|
||||
status: completed
|
||||
- id: fix-password-dialog
|
||||
content: "Edit.vue: 密码字段改为弹窗独立修改,主表单提交排除密码数据"
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 修复 Table 重复请求、搜索防抖、Edit 密码弹窗
|
||||
|
||||
## 1. 修复重复请求 - [Table.vue](d:\work\myst-web\src\components\Table.vue)
|
||||
|
||||
**根因**:`mounted()` 中既修改了 form 属性(触发 deep watcher),又显式调用了 `formDebounce`,两者可能各触发一次请求。
|
||||
|
||||
**方案**:在 data 中增加 `_skipFormWatch: true` 标志,mounted 中所有 form 初始化完成后调用一次 `formDebounce`,然后在 `$nextTick` 中将标志设为 false。watcher 中检查该标志,初始化期间跳过。
|
||||
|
||||
```javascript
|
||||
// data 中
|
||||
_skipFormWatch: true,
|
||||
|
||||
// watcher 中
|
||||
form: {
|
||||
handler() {
|
||||
if (this._skipFormWatch) return;
|
||||
this.formDebounce(this);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
|
||||
// mounted 末尾
|
||||
this.formDebounce(this);
|
||||
this.$nextTick(() => { this._skipFormWatch = false; });
|
||||
```
|
||||
|
||||
## 2. 搜索改为点击触发 - [Table.vue](d:\work\myst-web\src\components\Table.vue)
|
||||
|
||||
**方案**:将关键字搜索从实时触发改为点击搜索按钮触发。
|
||||
|
||||
- 新增 `searchKeyword: ""` data 属性,输入框 `v-model` 改为绑定 `searchKeyword`
|
||||
- 搜索按钮添加 `@click="doSearch"` 事件
|
||||
- `doSearch` 方法将 `searchKeyword` 赋值给 `form.keyword`(触发 watcher 进行搜索)
|
||||
- 输入框支持 Enter 键触发搜索(`@keyup.enter`)
|
||||
- 重置按钮也同步清空 `searchKeyword`
|
||||
|
||||
关键模板改动:
|
||||
|
||||
```html
|
||||
<el-input v-model="searchKeyword" @keyup.enter="doSearch" ...>
|
||||
<template #append>
|
||||
<el-button class="search-btn" @click="doSearch">
|
||||
<el-icon><Search/></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
```
|
||||
|
||||
## 3. 密码改为弹窗独立修改 - [Edit.vue](d:\work\myst-web\src\components\Edit.vue)
|
||||
|
||||
**方案**:
|
||||
|
||||
- 主表单中隐藏 password 类型字段,改为显示"修改密码"按钮
|
||||
- 新增密码修改弹窗(`el-dialog`),包含新密码 + 确认密码两个输入
|
||||
- 弹窗提交时校验两次输入一致,然后仅发送密码字段调用 `edit` API
|
||||
- 主表单 `onSubmit` 提交数据时过滤掉 password 类型字段
|
||||
|
||||
关键改动点:
|
||||
|
||||
**模板** - 密码字段区域(约第55行):
|
||||
|
||||
```html
|
||||
<!-- 原密码输入改为修改密码按钮 -->
|
||||
<el-button v-if="column.type=='password'" type="warning" @click="showPasswordDialog = true">
|
||||
修改密码
|
||||
</el-button>
|
||||
<!-- 其他非密码字段保持原样 -->
|
||||
<el-input v-model="form[column.value]" show-password v-else-if="..." />
|
||||
```
|
||||
|
||||
**data** 新增:
|
||||
|
||||
```javascript
|
||||
showPasswordDialog: false,
|
||||
passwordForm: { password: '', confirmPassword: '' },
|
||||
passwordColumn: null,
|
||||
```
|
||||
|
||||
**methods** 新增 `submitPassword` 和修改 `onSubmit`:
|
||||
|
||||
- `submitPassword`:校验密码一致性,调用 `edit` API 仅提交密码字段
|
||||
- `onSubmit`:提交时从 form 中排除 password 类型字段的数据
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: 修复达梦数据库越界和引号
|
||||
overview: 修复 HoTime 框架代码生成器的两个达梦(DM)数据库问题:(1) 获取表列表时越界包含了系统表;(2) SQL 查询使用了 MySQL 反引号语法导致达梦解析失败。
|
||||
todos:
|
||||
- id: fix-dm-table-filter
|
||||
content: 修改 code/makecode.go 的达梦表列表和列信息查询,使用 ALL_TAB_COMMENTS + OWNER 过滤
|
||||
status: completed
|
||||
- id: fix-backtick-convert
|
||||
content: 修改 db/identifier.go 的 ProcessFieldList,增加 convertQuotes 调用转换残留反引号
|
||||
status: completed
|
||||
- id: regen-admin-json
|
||||
content: 删除/清理 myst-go/config/admin.json 中的系统表条目,重新生成配置
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 修复达梦数据库越界获取系统表和反引号语法错误
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 问题 1:越界获取非 ZWBG 数据库的表
|
||||
|
||||
[code/makecode.go](d:\work\hotimev1.5\code\makecode.go) 第 121-122 行:
|
||||
|
||||
```go
|
||||
nowTables = db.Query(`SELECT TABLE_NAME AS "name", COMMENTS AS "label" FROM USER_TAB_COMMENTS WHERE TABLE_TYPE='TABLE'`)
|
||||
```
|
||||
|
||||
使用 `SYSDBA` 用户连接达梦时,`USER_TAB_COMMENTS` 返回该用户下**所有表**,包括达梦系统表(`SYSJOBS`、`SYSJOBSCHEDULES`、`SYSALERTS`、`##HISTOGRAMS_TABLE` 等),导致 [admin.json](d:\work\myst-go\config\admin.json) 中混入了大量不属于 ZWBG 业务的系统表。
|
||||
|
||||
同样的问题存在于第 169-170 行的列信息查询(`USER_TAB_COLUMNS` / `USER_COL_COMMENTS`)。
|
||||
|
||||
### 问题 2:反引号导致 SQL 语法错误(遗留问题)
|
||||
|
||||
`makecode.go` 中 `Info`(第 725 行)和 `Search`(第 958 行)使用反引号拼接列名字符串:``col1`,`col2`,...`。
|
||||
|
||||
`crud.go` 的 `Select` 方法有**两种字段处理路径**:
|
||||
|
||||
- **Slice 路径**(第 100-109 行):每个列名走 `ProcessColumnNoPrefix` -> `QuoteIdentifier`,能正确转换引号
|
||||
- **String 路径**(第 93-99 行):整个字符串走 `ProcessFieldList` -> `ProcessConditionString`,三个正则都只匹配 `table.column` 格式(含 `.`),不处理单独的反引号列名
|
||||
|
||||
`makecode.go` 传的是拼接好的**字符串**,走的是 String 路径,所以反引号不会被自动转换,导致达梦报语法错误。
|
||||
|
||||
---
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 修改 1:过滤达梦系统表 - [code/makecode.go](d:\work\hotimev1.5\code\makecode.go)
|
||||
|
||||
将 `USER_TAB_COMMENTS` / `USER_TAB_COLUMNS` 改为 `ALL_TAB_COMMENTS` / `ALL_TAB_COLUMNS`,并通过 `OWNER` 过滤为 `db.DBName`(即 `ZWBG`),确保只获取目标 schema 的业务表。
|
||||
|
||||
**第 121-122 行**,表列表查询改为:
|
||||
|
||||
```go
|
||||
if db.Type == "dm" || db.Type == "dameng" {
|
||||
nowTables = db.Query(`SELECT TABLE_NAME AS "name", COMMENTS AS "label" FROM ALL_TAB_COMMENTS WHERE TABLE_TYPE='TABLE' AND OWNER='` + strings.ToUpper(db.DBName) + `'`)
|
||||
}
|
||||
```
|
||||
|
||||
**第 169-170 行**,列信息查询改为:
|
||||
|
||||
```go
|
||||
if db.Type == "dm" || db.Type == "dameng" {
|
||||
tableInfo = db.Query(`SELECT c.COLUMN_NAME AS "name", c.DATA_TYPE AS "type", m.COMMENTS AS "label", c.NULLABLE AS "must", c.DATA_DEFAULT AS "dflt_value" FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS m ON c.TABLE_NAME=m.TABLE_NAME AND c.COLUMN_NAME=m.COLUMN_NAME AND c.OWNER=m.OWNER WHERE c.TABLE_NAME='` + v.GetString("name") + `' AND c.OWNER='` + strings.ToUpper(db.DBName) + `' ORDER BY c.COLUMN_ID`)
|
||||
}
|
||||
```
|
||||
|
||||
### 修改 2:修复反引号转换 - [db/identifier.go](d:\work\hotimev1.5\db\identifier.go)
|
||||
|
||||
`makecode.go` 的反引号属于遗留代码,理论上 `Select` 的 Slice 路径已能自动处理引号,但 `makecode.go` 传的是字符串。最小改动:在 `ProcessFieldList` 末尾加 `convertQuotes` 调用,将 `ProcessConditionString` 未能匹配到的残留反引号统一转为当前方言引号:
|
||||
|
||||
```go
|
||||
func (p *IdentifierProcessor) ProcessFieldList(fields string) string {
|
||||
if fields == "" || fields == "*" {
|
||||
return fields
|
||||
}
|
||||
result := p.ProcessConditionString(fields)
|
||||
result = p.convertQuotes(result)
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
`convertQuotes`(第 240-248 行)已实现引号转换:达梦将反引号转为 `"`,MySQL 保持不变(no-op)。这样无需修改 `makecode.go` 中的遗留反引号代码,所有走 `ProcessFieldList` 的字符串都能被正确处理。
|
||||
|
||||
### 修改 3:重新生成 admin.json - [config/admin.json](d:\work\myst-go\config\admin.json)
|
||||
|
||||
代码修复后,需要删除当前 `admin.json`(已包含系统表污染)并重启应用让其自动重新生成。需要注意的是:
|
||||
|
||||
- 如果 `admin.json` 中有手工定制的菜单/权限配置,**需先备份**再删除
|
||||
- 或者手动从 `admin.json` 中删除以下系统表条目:`SYSJOBSCHEDULES`、`SYSJOBSTEPS`、`SYSJOBHISTORIES`、`SYSJOBHISTORIES2`、`SYSSTEPHISTORIES2`、`SYSALERTHISTORIES`、`SYSALERTNOTIFICATIONS`、`SYSALERTS`、`SYSMAILINFO`、`SYSOPERATORS`、`SYSJOBS`、`##HISTOGRAMS_TABLE`、`cached`、`hotime_cache`、`test_batch`
|
||||
|
||||
---
|
||||
|
||||
## 数据流概览
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph code_gen [代码生成 - makecode.go]
|
||||
A["Db2JSON()"] -->|"USER_TAB_COMMENTS (修复前)"| B["获取所有表含系统表"]
|
||||
A -->|"ALL_TAB_COMMENTS + OWNER (修复后)"| C["仅获取ZWBG业务表"]
|
||||
end
|
||||
subgraph query [查询执行 - code.go + crud.go]
|
||||
D["Info/Search 拼字段"] -->|反引号列名| E["ProcessFieldList"]
|
||||
E -->|"ProcessConditionString (不匹配)"| F["反引号保留 -> DM报错"]
|
||||
E -->|"+ convertQuotes (修复后)"| G["转为双引号 -> DM正常"]
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
name: 字段备注空格扩展
|
||||
overview: 在 hotime 的列注释解析中,除保留「首段为系统内 label」外,将「第一个空格之后、选项/花括号规则之前」的内容单独写入新字段(如 remark),供前端展示;并澄清:管理端若列来自已有配置或 JSON,可能未经过 DB 新列分支,故不会表现为「整段 label 被丢掉」。
|
||||
todos:
|
||||
- id: parse-makecode
|
||||
content: 在 makecode.go 中实现 SplitN + 空格拆分 + remark 字段,并统一 options/ps 解析顺序
|
||||
status: pending
|
||||
- id: ui-remark
|
||||
content: 在 myst-web Edit/Info 中展示 column.remark(可选样式)
|
||||
status: pending
|
||||
- id: docs-format
|
||||
content: 补充列注释格式说明文档
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 表字段备注:空格后扩展备注(修正认知)
|
||||
|
||||
## 为何你能在管理端看到「完整」label
|
||||
|
||||
`[hotimev1.5/code/makecode.go](d:/work/hotimev1.5/code/makecode.go)` 中,**空格截断与 `:` 处理仅发生在 `if coloum == nil` 分支**(约 189–325 行),即:**仅当该列尚未在 `TableColumns` 中存在、完全由本次从库表结构推断新建列时**才会执行。
|
||||
|
||||
若列定义已来自 `[config/adminDB.json](d:/work/myst-go/config/adminDB.json)` 等已有配置,会走 `coloum != nil`,**不会执行**上述截断逻辑,管理端下发的 `column.label` 仍可能是你在 JSON 里写的整段字符串,因此**不会出现「整段在 UI 里被丢掉」的现象**。
|
||||
|
||||
你遇到的「断言/解析」问题更可能出在:**需要同时支持「系统内短 label」与「空格后的说明」两种语义**,而不是单纯丢失数据。
|
||||
|
||||
## 目标行为(约定)
|
||||
|
||||
对**数据库列注释**(以及与之对齐的生成逻辑)采用统一解析顺序,避免与现有 `:` 选项、`{...}` 的 `ps` 冲突:
|
||||
|
||||
1. 用 `**SplitN(raw, ":", 2)`** 拆出「主文案」与「选项串」(替代当前对整串 `strings.Split(..., ":")`,避免备注里出现冒号时误伤)。
|
||||
2. 在「主文案」中按 **第一个空格** 拆成:
|
||||
- **第一段**:`label`(系统内字段名/短标题,与现有一致)
|
||||
- **第二段及以后**(或仅第二段,按产品定):trim 后写入新字段,建议键名 `**remark`**(若你更倾向 `note` / `labelRemark` 可再定)
|
||||
3. `**{...}` → `ps`**:仍在**完整主文案或完整 raw** 上提取(与现有行为一致),避免破坏依赖 `ps` 的表单。
|
||||
4. **选项解析**:使用 `SplitN` 的第二段,保持 `1-男,2-女` 这类格式不变。
|
||||
|
||||
示例:`姓名 对外展示说明 :1-男,2-女` → `label=姓名`,`remark=对外展示说明`,options 从 `:1-男,2-女` 解析。
|
||||
|
||||
## 实现位置
|
||||
|
||||
|
||||
| 层级 | 文件 | 内容 |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| 后端 | `[hotimev1.5/code/makecode.go](d:/work/hotimev1.5/code/makecode.go)` | 重构 218–233 与 300–308 行附近逻辑:先 `:` 再空格,并设置 `coloum["remark"]`;无空格时行为与现有一致 |
|
||||
| 前端(可选但推荐) | `[myst-web/src/components/Edit.vue](d:/work/myst-web/src/components/Edit.vue)`、`[Info.vue](d:/work/myst-web/src/components/Info.vue)` | 在表单项标签旁或下方展示 `column.remark`(小字/灰色提示),无则不占位 |
|
||||
| 文档 | `[myst-go/docs/](d:/work/myst-go/docs/)` 或 QUICKSTART | 简短说明列注释格式:`label 备注 :选项` |
|
||||
|
||||
|
||||
依赖关系:`[myst-go/go.mod](d:/work/myst-go/go.mod)` 中 `replace code.hoteas.com/golang/hotime => ../hotimev1.5`,改 hotime 源码即可被 myst-go 使用。
|
||||
|
||||
## 兼容与回归
|
||||
|
||||
- **无空格**、仅 `label:选项`、仅 `{ps}`:行为与现网一致。
|
||||
- **adminDB.json 手工写的长 label**:若需同样拆分,可在后续加「对已有列合并 DB 注释时补写 remark」或提供一次性脚本;首版以 **Db2JSON 新列路径** 与 **重新生成/同步列** 为主。
|
||||
|
||||
## 流程示意
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
raw[DB列注释raw]
|
||||
raw --> splitColon["SplitN 第一处冒号"]
|
||||
splitColon --> mainPart[主文案]
|
||||
splitColon --> optPart[选项串]
|
||||
main --> splitSpace["第一处空格"]
|
||||
splitSpace --> label[label]
|
||||
splitSpace --> remark[remark]
|
||||
raw --> psBrace["提取 ps 花括号"]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: 字段提示括号扩展
|
||||
overview: 在后端解析数据库列注释中的 `()` 内容作为字段提示(ps),修复空格截断 bug,并在前端 Table/Info/Edit 组件中展示提示信息。
|
||||
todos:
|
||||
- id: backend-parse
|
||||
content: 后端 makecode.go:新增 () 解析,调整解析顺序(先提取括号内容,再空格/冒号截断),存入 ps 字段
|
||||
status: completed
|
||||
- id: backend-fix-space
|
||||
content: 后端 makecode.go:修复 coloum != nil 分支中 label 未截断空格的问题
|
||||
status: completed
|
||||
- id: frontend-edit
|
||||
content: 前端 Edit.vue / Add.vue:输入框右侧显示 column.ps 提示文字
|
||||
status: completed
|
||||
- id: frontend-table
|
||||
content: 前端 Table.vue:列头加 el-tooltip 展示 td.ps
|
||||
status: completed
|
||||
- id: frontend-info
|
||||
content: 前端 Info.vue:详情字段名加 tooltip 展示 column.ps
|
||||
status: completed
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# 字段提示:支持 () 括号 + 前端展示
|
||||
|
||||
## 现状问题
|
||||
|
||||
1. 后端已经能从 `{}` 提取 `ps`,但前端从未使用 `ps` 字段
|
||||
2. `{}` 写法不直观,用户希望用更常见的 `()` 来写字段提示
|
||||
3. 管理端某些列的 label 包含了空格后面的文字(未经截断)
|
||||
|
||||
## 数据库注释格式约定(举例)
|
||||
|
||||
|
||||
| 注释写法 | label | ps | options |
|
||||
| --------------------------- | ----- | ------- | -------------- |
|
||||
| `姓名` | 姓名 | 无 | 无 |
|
||||
| `状态(当前审核状态)` | 状态 | 当前审核状态 | 无 |
|
||||
| `性别:1-男,2-女` | 性别 | 无 | 男/女 |
|
||||
| `状态(当前审核状态):1-待审,2-通过,3-驳回` | 状态 | 当前审核状态 | 待审/通过/驳回 |
|
||||
| `备注{请输入详细说明}` | 备注 | 请输入详细说明 | 无 |
|
||||
| `姓名 xxx说明` | 姓名 | 无 | 无(空格后丢弃,同现有行为) |
|
||||
|
||||
|
||||
`()` 和 `{}` 都提取到同一个 `ps` 字段(功能一致,只是写法不同)。
|
||||
|
||||
## 后端改动
|
||||
|
||||
文件:[hotimev1.5/code/makecode.go](d:/work/hotimev1.5/code/makecode.go) 约 218-233 行
|
||||
|
||||
当前解析顺序是:空格截断 -> 花括号提取ps -> 冒号截断。需要调整为:
|
||||
|
||||
1. **新增 `()` 解析**:在原有 `{}` 提取 ps 的逻辑旁边,增加对 `(` 和 `)` 的检测,提取内容同样存入 `coloum["ps"]`
|
||||
2. **调整解析顺序**:先提取 `()` / `{}`(从原始注释中),再做空格截断和冒号截断,避免截断后丢失括号内容
|
||||
3. **提取后清理 label**:将 `(...)` 或 `{...}` 从 label 中移除,确保 label 干净
|
||||
|
||||
改后伪逻辑(自然语言):
|
||||
|
||||
```
|
||||
原始注释 = "状态(当前审核状态):1-待审,2-通过,3-驳回"
|
||||
|
||||
第一步:从原始注释中找 () 或 {},提取内容 → ps = "当前审核状态"
|
||||
第二步:把 (...) 或 {...} 从注释中去掉 → "状态:1-待审,2-通过,3-驳回"
|
||||
第三步:空格截断(取第一个空格前的部分)
|
||||
第四步:冒号截断 label → label = "状态"
|
||||
第五步:冒号后面解析选项 → options
|
||||
```
|
||||
|
||||
## 前端改动
|
||||
|
||||
### 1. Edit.vue / Add.vue — 输入框右侧显示提示
|
||||
|
||||
在 `el-form-item` 内、输入控件后面追加一个小字提示:
|
||||
|
||||
```html
|
||||
<span v-if="column.ps" class="field-hint">{{ column.ps }}</span>
|
||||
```
|
||||
|
||||
样式为灰色小字,紧贴输入框右侧。
|
||||
|
||||
### 2. Table.vue — 列头 tooltip
|
||||
|
||||
给 `el-table-column` 加 header 插槽,当 `td.ps` 存在时用 `el-tooltip` 包裹列头文字:
|
||||
|
||||
```html
|
||||
<template #header v-if="td.ps">
|
||||
<el-tooltip :content="td.ps" placement="top">
|
||||
<span>{{ td.label }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
```
|
||||
|
||||
无 `ps` 的列保持原样。
|
||||
|
||||
### 3. Info.vue — 字段名 tooltip
|
||||
|
||||
类似地,在详情页的 `el-descriptions-item` 的 label 上加 tooltip 展示 `column.ps`。
|
||||
|
||||
## 关于"管理端空格后 label 未截断"的 bug
|
||||
|
||||
这个问题出在:当列已存在于配置(`coloum != nil` 分支),不会执行空格截断逻辑。如果 adminDB.json 里的 label 本身就包含空格后的文字,它会原样显示。
|
||||
|
||||
修复方式:在 makecode.go 的 `coloum != nil` 分支(即已有配置的列),也对 label 做同样的空格截断和 `()` / `{}` 提取处理。或者确认 adminDB.json 中的 label 是否需要手动清理。需要先确认具体是哪些列出现了这个问题。
|
||||
+18
-4
@@ -42,20 +42,34 @@ var DefaultMenuParentName = "sys"
|
||||
|
||||
var ColumnDataType = map[string]string{
|
||||
//sqlite专有类型
|
||||
"real": "number",
|
||||
//mysql数据类型宽泛类型
|
||||
"real": "number",
|
||||
"integer": "number",
|
||||
//mysql数据类型
|
||||
"int": "number",
|
||||
"float": "number",
|
||||
"double": "number",
|
||||
"decimal": "number",
|
||||
"integer": "number", //sqlite3
|
||||
"char": "text",
|
||||
"text": "text",
|
||||
"blob": "text",
|
||||
"date": "time",
|
||||
"time": "time",
|
||||
"year": "time",
|
||||
"geometry": "gis", //不建议使用gis类型,建议使用其他代替
|
||||
"bit": "number",
|
||||
"binary": "text",
|
||||
"geometry": "gis",
|
||||
//postgresql数据类型
|
||||
"numeric": "number",
|
||||
"boolean": "number",
|
||||
"serial": "number",
|
||||
"json": "text",
|
||||
"uuid": "text",
|
||||
"bytea": "text",
|
||||
"interval": "text",
|
||||
"money": "number",
|
||||
//达梦(DM)/Oracle数据类型
|
||||
"clob": "text",
|
||||
"number": "number",
|
||||
}
|
||||
|
||||
type ColumnShow struct {
|
||||
|
||||
+58
-20
@@ -119,7 +119,7 @@ func (that *MakeCode) Db2JSON(db *db.HoTimeDB, config Map) {
|
||||
nowTables = db.Select("sqlite_master", "name", Map{"type": "table"})
|
||||
}
|
||||
if db.Type == "dm" || db.Type == "dameng" {
|
||||
nowTables = db.Query(`SELECT TABLE_NAME AS "name", COMMENTS AS "label" FROM USER_TAB_COMMENTS WHERE TABLE_TYPE='TABLE'`)
|
||||
nowTables = db.Query(`SELECT TABLE_NAME AS "name", COMMENTS AS "label" FROM ALL_TAB_COMMENTS WHERE TABLE_TYPE='TABLE' AND OWNER='` + db.DBName + `'`)
|
||||
}
|
||||
//idSlice=append(idSlice,nowTables)
|
||||
for _, v := range nowTables {
|
||||
@@ -130,6 +130,14 @@ func (that *MakeCode) Db2JSON(db *db.HoTimeDB, config Map) {
|
||||
if v.GetString("label") == "" {
|
||||
v["label"] = v.GetString("name")
|
||||
}
|
||||
if strings.HasSuffix(v.GetString("label"), "表") {
|
||||
withoutTable := strings.TrimSuffix(v.GetString("label"), "表")
|
||||
if len([]rune(withoutTable)) <= 3 {
|
||||
v["label"] = withoutTable + "管理"
|
||||
} else {
|
||||
v["label"] = withoutTable
|
||||
}
|
||||
}
|
||||
that.TableConfig[v.GetString("name")] = Map{
|
||||
"label": v.GetString("label"),
|
||||
"table": v.GetString("name"),
|
||||
@@ -167,7 +175,7 @@ func (that *MakeCode) Db2JSON(db *db.HoTimeDB, config Map) {
|
||||
tableInfo = db.Query("pragma table_info([" + v.GetString("name") + "]);")
|
||||
}
|
||||
if db.Type == "dm" || db.Type == "dameng" {
|
||||
tableInfo = db.Query(`SELECT c.COLUMN_NAME AS "name", c.DATA_TYPE AS "type", m.COMMENTS AS "label", c.NULLABLE AS "must", c.DATA_DEFAULT AS "dflt_value" FROM USER_TAB_COLUMNS c LEFT JOIN USER_COL_COMMENTS m ON c.TABLE_NAME=m.TABLE_NAME AND c.COLUMN_NAME=m.COLUMN_NAME WHERE c.TABLE_NAME='` + v.GetString("name") + `' ORDER BY c.COLUMN_ID`)
|
||||
tableInfo = db.Query(`SELECT c.COLUMN_NAME AS "name", c.DATA_TYPE AS "type", m.COMMENTS AS "label", c.NULLABLE AS "must", c.DATA_DEFAULT AS "dflt_value" FROM ALL_TAB_COLUMNS c LEFT JOIN USER_COL_COMMENTS m ON c.TABLE_NAME=m.TABLE_NAME AND c.COLUMN_NAME=m.COLUMN_NAME WHERE c.TABLE_NAME='` + v.GetString("name") + `' AND c.OWNER='` + db.DBName + `' ORDER BY c.COLUMN_ID`)
|
||||
}
|
||||
|
||||
idSlice = append(idSlice, tableInfo)
|
||||
@@ -181,7 +189,7 @@ func (that *MakeCode) Db2JSON(db *db.HoTimeDB, config Map) {
|
||||
if coloum == nil {
|
||||
//根据类型判断真实类型
|
||||
for k, v1 := range ColumnDataType {
|
||||
if strings.Contains(info.GetString("type"), k) {
|
||||
if strings.Contains(strings.ToLower(info.GetString("type")), k) {
|
||||
info["type"] = v1
|
||||
break
|
||||
}
|
||||
@@ -207,22 +215,26 @@ func (that *MakeCode) Db2JSON(db *db.HoTimeDB, config Map) {
|
||||
coloum["must"] = true
|
||||
}
|
||||
|
||||
//备注以空格隔开,空格后的是其他备注
|
||||
indexNum := strings.Index(info.GetString("label"), " ")
|
||||
if indexNum > -1 {
|
||||
coloum["label"] = info.GetString("label")[:indexNum]
|
||||
//提取括号内的提示信息:支持 ()、()、{}
|
||||
rawLabel := info.GetString("label")
|
||||
for _, pair := range [][2]string{{"(", ")"}, {"(", ")"}, {"{", "}"}} {
|
||||
start := strings.Index(rawLabel, pair[0])
|
||||
end := strings.Index(rawLabel, pair[1])
|
||||
if start != -1 && end > start {
|
||||
coloum["ps"] = rawLabel[start+len(pair[0]) : end]
|
||||
rawLabel = rawLabel[:start] + rawLabel[end+len(pair[1]):]
|
||||
break
|
||||
}
|
||||
}
|
||||
psStart := strings.Index(info.GetString("label"), "{")
|
||||
psEnd := strings.Index(info.GetString("label"), "}")
|
||||
if psStart != -1 && psEnd > psStart {
|
||||
coloum["ps"] = info.GetString("label")[psStart+1 : psEnd]
|
||||
//空格截断,空格后的内容丢弃
|
||||
if idx := strings.Index(rawLabel, " "); idx > -1 {
|
||||
rawLabel = rawLabel[:idx]
|
||||
}
|
||||
|
||||
//去除数据信息,是用:号分割的
|
||||
indexNum = strings.Index(coloum.GetString("label"), ":")
|
||||
if indexNum > -1 {
|
||||
coloum["label"] = coloum.GetString("label")[:indexNum]
|
||||
//冒号截断,去除选项数据部分
|
||||
if idx := strings.Index(rawLabel, ":"); idx > -1 {
|
||||
rawLabel = rawLabel[:idx]
|
||||
}
|
||||
coloum["label"] = rawLabel
|
||||
|
||||
for _, ColumnName := range that.RuleConfig {
|
||||
if (ColumnName.GetBool("strict") && coloum.GetString("name") == ColumnName.GetString("name")) ||
|
||||
@@ -311,6 +323,25 @@ func (that *MakeCode) Db2JSON(db *db.HoTimeDB, config Map) {
|
||||
}
|
||||
//}
|
||||
|
||||
} else {
|
||||
//对已有配置列也提取 ps 并清理 label
|
||||
rawLabel := coloum.GetString("label")
|
||||
for _, pair := range [][2]string{{"(", ")"}, {"(", ")"}, {"{", "}"}} {
|
||||
start := strings.Index(rawLabel, pair[0])
|
||||
end := strings.Index(rawLabel, pair[1])
|
||||
if start != -1 && end > start {
|
||||
coloum["ps"] = rawLabel[start+len(pair[0]) : end]
|
||||
rawLabel = rawLabel[:start] + rawLabel[end+len(pair[1]):]
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx := strings.Index(rawLabel, " "); idx > -1 {
|
||||
rawLabel = rawLabel[:idx]
|
||||
}
|
||||
if idx := strings.Index(rawLabel, ":"); idx > -1 {
|
||||
rawLabel = rawLabel[:idx]
|
||||
}
|
||||
coloum["label"] = rawLabel
|
||||
}
|
||||
|
||||
//暂时不关闭参数,保证表数据完全读取到
|
||||
@@ -803,7 +834,7 @@ func (that *MakeCode) Add(table string, user Map, req *http.Request) Map {
|
||||
data[v.GetString("name")] = time.Now().Unix()
|
||||
}
|
||||
if v.GetString("type") == "time" {
|
||||
data[v.GetString("name")] = time.Now().Format("2006-01-02 15-04-05")
|
||||
data[v.GetString("name")] = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -813,7 +844,7 @@ func (that *MakeCode) Add(table string, user Map, req *http.Request) Map {
|
||||
data[v.GetString("name")] = time.Now().Unix()
|
||||
}
|
||||
if v.GetString("type") == "time" {
|
||||
data[v.GetString("name")] = time.Now().Format("2006-01-02 15-04-05")
|
||||
data[v.GetString("name")] = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -862,7 +893,7 @@ func (that *MakeCode) Edit(table string, req *http.Request) Map {
|
||||
data[v.GetString("name")] = time.Now().Unix()
|
||||
}
|
||||
if v.GetString("type") == "time" {
|
||||
data[v.GetString("name")] = time.Now().Format("2006-01-02 15-04-05")
|
||||
data[v.GetString("name")] = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1178,7 +1209,14 @@ func (that *MakeCode) Search(table string, userData Map, data Map, req *http.Req
|
||||
}
|
||||
|
||||
if len(reqValue) != 0 && searchItem.GetString("name") == "sort" && reqValue[0] != "" {
|
||||
sortMap["ORDER"] = reqValue[0]
|
||||
parts := strings.Fields(reqValue[0])
|
||||
if len(parts) == 2 {
|
||||
col := parts[0]
|
||||
dir := strings.ToUpper(parts[1])
|
||||
if (dir == "ASC" || dir == "DESC") && that.TableColumns[table][col] != nil {
|
||||
sortMap["ORDER"] = col + " " + dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
+3
-2
@@ -187,8 +187,9 @@ func (p *IdentifierProcessor) ProcessFieldList(fields string) string {
|
||||
return fields
|
||||
}
|
||||
|
||||
// 使用与 ProcessConditionString 相同的逻辑
|
||||
return p.ProcessConditionString(fields)
|
||||
result := p.ProcessConditionString(fields)
|
||||
result = p.convertQuotes(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// stripQuotes 去除标识符两端的引号(反引号或双引号)
|
||||
|
||||
+10
-3
@@ -148,12 +148,15 @@ func main() {
|
||||
支持从数据库字段备注中提取信息:
|
||||
|
||||
```sql
|
||||
-- 字段备注格式: 标签名:选项1-名称1,选项2-名称2 {提示信息}
|
||||
-- 字段备注格式: 标签名(提示信息):选项1-名称1,选项2-名称2
|
||||
-- 括号支持三种写法:()、()、{}
|
||||
-- 例如:
|
||||
status TINYINT COMMENT '状态:0-禁用,1-启用 {用户账号状态}'
|
||||
status TINYINT COMMENT '状态(用户账号状态):0-禁用,1-启用'
|
||||
parent_id INT COMMENT '父级ID(顶级为NULL)'
|
||||
phone VARCHAR(20) COMMENT '手机号(请输入11位手机号)'
|
||||
```
|
||||
|
||||
生成的配置:
|
||||
生成的配置(以第一行为例):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -168,6 +171,10 @@ status TINYINT COMMENT '状态:0-禁用,1-启用 {用户账号状态}'
|
||||
}
|
||||
```
|
||||
|
||||
`ps` 字段在前端的展示效果:
|
||||
- 编辑/新增页面:输入框右侧灰色小字提示
|
||||
- 表格/详情页面:鼠标悬停字段名时气泡提示
|
||||
|
||||
---
|
||||
|
||||
## 自定义规则
|
||||
|
||||
@@ -229,15 +229,17 @@ SQLite 不支持表备注,代码生成器会使用表名作为默认显示名
|
||||
字段注释支持以下格式组合:
|
||||
|
||||
```
|
||||
显示名称:选项值 附加备注{前端提示}
|
||||
显示名称(前端提示):选项值 附加备注
|
||||
显示名称(前端提示):选项值 附加备注
|
||||
显示名称{前端提示}:选项值 附加备注
|
||||
```
|
||||
|
||||
| 部分 | 分隔符 | 用途 |
|
||||
|------|--------|------|
|
||||
| 显示名称 | 无 | 前端表单/列表的字段标签 |
|
||||
| 前端提示 | `()`、`()` 或 `{}` | 存储到 `ps` 字段,前端用于字段说明提示(编辑页输入框右侧、表格/详情鼠标悬停气泡) |
|
||||
| 选项值 | `:` 冒号 | select 类型的下拉选项(格式:`值-名称,值-名称`) |
|
||||
| 附加备注 | 空格 | 仅在数据库中查看,不传递给前端 |
|
||||
| 前端提示 | `{}` | 存储到 `ps` 字段,用于前端显示提示文字 |
|
||||
|
||||
### 选择类型字段
|
||||
|
||||
@@ -268,28 +270,38 @@ SQLite 不支持表备注,代码生成器会使用表名作为默认显示名
|
||||
`amount` decimal(10,2) COMMENT '金额 单位为元,精确到分'
|
||||
```
|
||||
|
||||
### 前端提示({}内)
|
||||
### 前端提示(括号内)
|
||||
|
||||
`{}` 中的内容存储到 `ps` 字段,前端可用于显示输入提示:
|
||||
`()`、`()` 或 `{}` 中的内容存储到 `ps` 字段,前端展示为字段说明提示。推荐使用 `()` 或 `()`,`{}` 为兼容旧格式保留。
|
||||
|
||||
```sql
|
||||
`phone` varchar(20) COMMENT '手机号{请输入11位手机号}'
|
||||
`parent_id` int(11) COMMENT '父级ID(顶级为NULL)'
|
||||
`status` int(2) COMMENT '状态(当前审核状态):0-待审,1-通过,2-驳回'
|
||||
`phone` varchar(20) COMMENT '手机号(请输入11位手机号)'
|
||||
`email` varchar(100) COMMENT '邮箱{格式:xxx@xxx.com}'
|
||||
`content` text COMMENT '内容{支持HTML格式}'
|
||||
```
|
||||
|
||||
前端展示效果:
|
||||
- **编辑/新增页面**:输入框右侧显示灰色小字提示
|
||||
- **表格列表/详情页面**:鼠标悬停字段名时显示气泡提示
|
||||
|
||||
### 组合使用
|
||||
|
||||
```sql
|
||||
-- 完整格式示例
|
||||
`status` int(2) DEFAULT '0' COMMENT '状态:0-待审核,1-已通过,2-已拒绝 业务状态流转字段{选择当前审核状态}'
|
||||
-- 推荐写法:使用圆括号
|
||||
`status` int(2) DEFAULT '0' COMMENT '状态(选择当前审核状态):0-待审核,1-已通过,2-已拒绝'
|
||||
`parent_id` int(11) DEFAULT NULL COMMENT '父级ID(顶级为NULL)'
|
||||
|
||||
-- 兼容写法:使用花括号
|
||||
`status` int(2) DEFAULT '0' COMMENT '状态{选择当前审核状态}:0-待审核,1-已通过,2-已拒绝'
|
||||
```
|
||||
|
||||
解析结果:
|
||||
解析结果(以第一行为例):
|
||||
- `label` = "状态"
|
||||
- `options` = [{name:"待审核", value:"0"}, {name:"已通过", value:"1"}, {name:"已拒绝", value:"2"}]
|
||||
- `ps` = "选择当前审核状态"
|
||||
- 空格后的"业务状态流转字段"不会传递给前端
|
||||
- `options` = [{name:"待审核", value:"0"}, {name:"已通过", value:"1"}, {name:"已拒绝", value:"2"}]
|
||||
|
||||
三种括号 `()`、`()`、`{}` 功能相同,优先匹配顺序为:`()` > `()` > `{}`,同一注释中只取第一个匹配到的括号对。
|
||||
|
||||
### SQLite 字段备注
|
||||
|
||||
|
||||
Reference in New Issue
Block a user