From 3681564ca459ae62179e123c9a952abd0e74a88f Mon Sep 17 00:00:00 2001
From: hoteas <925970985@qq.com>
Date: Mon, 23 Mar 2026 16:48:56 +0800
Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=89=A9=E5=B1=95=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E7=B1=BB=E5=9E=8B=E6=98=A0=E5=B0=84=E4=B8=8E=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=AD=97=E6=AE=B5=E5=A4=87=E6=B3=A8=E6=8F=90?=
=?UTF-8?q?=E5=8F=96=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在 ColumnDataType 中新增对 PostgreSQL 和 Oracle 数据类型的支持
- 更新 Db2JSON 方法,优化对达梦数据库表和列信息的查询逻辑
- 增强字段备注解析,支持从备注中提取提示信息并清理标签
- 更新文档,详细说明字段备注格式及前端提示的展示效果
---
.../plans/修复dm列注释读取_2231760d.plan.md | 50 ++++++++
.../修复dm类型映射大小写_b8477014.plan.md | 81 +++++++++++++
.../修复table和edit组件_aafba288.plan.md | 99 ++++++++++++++++
.../修复达梦数据库越界和引号_eafbbf76.plan.md | 110 ++++++++++++++++++
.../plans/字段备注空格扩展_e164bdb6.plan.md | 72 ++++++++++++
.../plans/字段提示括号扩展_91441aee.plan.md | 102 ++++++++++++++++
code/config.go | 22 +++-
code/makecode.go | 78 +++++++++----
db/identifier.go | 5 +-
docs/CodeGen_使用说明.md | 13 ++-
docs/DatabaseDesign_数据库设计规范.md | 34 ++++--
11 files changed, 626 insertions(+), 40 deletions(-)
create mode 100644 .cursor/plans/修复dm列注释读取_2231760d.plan.md
create mode 100644 .cursor/plans/修复dm类型映射大小写_b8477014.plan.md
create mode 100644 .cursor/plans/修复table和edit组件_aafba288.plan.md
create mode 100644 .cursor/plans/修复达梦数据库越界和引号_eafbbf76.plan.md
create mode 100644 .cursor/plans/字段备注空格扩展_e164bdb6.plan.md
create mode 100644 .cursor/plans/字段提示括号扩展_91441aee.plan.md
diff --git a/.cursor/plans/修复dm列注释读取_2231760d.plan.md b/.cursor/plans/修复dm列注释读取_2231760d.plan.md
new file mode 100644
index 0000000..676fcac
--- /dev/null
+++ b/.cursor/plans/修复dm列注释读取_2231760d.plan.md
@@ -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 不一致问题。
\ No newline at end of file
diff --git a/.cursor/plans/修复dm类型映射大小写_b8477014.plan.md b/.cursor/plans/修复dm类型映射大小写_b8477014.plan.md
new file mode 100644
index 0000000..696e517
--- /dev/null
+++ b/.cursor/plans/修复dm类型映射大小写_b8477014.plan.md
@@ -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",
+}
+```
+
diff --git a/.cursor/plans/修复table和edit组件_aafba288.plan.md b/.cursor/plans/修复table和edit组件_aafba288.plan.md
new file mode 100644
index 0000000..1465b01
--- /dev/null
+++ b/.cursor/plans/修复table和edit组件_aafba288.plan.md
@@ -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
+
+
+
+
+
+
+
+```
+
+## 3. 密码改为弹窗独立修改 - [Edit.vue](d:\work\myst-web\src\components\Edit.vue)
+
+**方案**:
+
+- 主表单中隐藏 password 类型字段,改为显示"修改密码"按钮
+- 新增密码修改弹窗(`el-dialog`),包含新密码 + 确认密码两个输入
+- 弹窗提交时校验两次输入一致,然后仅发送密码字段调用 `edit` API
+- 主表单 `onSubmit` 提交数据时过滤掉 password 类型字段
+
+关键改动点:
+
+**模板** - 密码字段区域(约第55行):
+
+```html
+
+
+ 修改密码
+
+
+
+```
+
+**data** 新增:
+
+```javascript
+showPasswordDialog: false,
+passwordForm: { password: '', confirmPassword: '' },
+passwordColumn: null,
+```
+
+**methods** 新增 `submitPassword` 和修改 `onSubmit`:
+
+- `submitPassword`:校验密码一致性,调用 `edit` API 仅提交密码字段
+- `onSubmit`:提交时从 form 中排除 password 类型字段的数据
+
diff --git a/.cursor/plans/修复达梦数据库越界和引号_eafbbf76.plan.md b/.cursor/plans/修复达梦数据库越界和引号_eafbbf76.plan.md
new file mode 100644
index 0000000..d6dbb58
--- /dev/null
+++ b/.cursor/plans/修复达梦数据库越界和引号_eafbbf76.plan.md
@@ -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
+```
+
+
+
diff --git a/.cursor/plans/字段备注空格扩展_e164bdb6.plan.md b/.cursor/plans/字段备注空格扩展_e164bdb6.plan.md
new file mode 100644
index 0000000..2915bce
--- /dev/null
+++ b/.cursor/plans/字段备注空格扩展_e164bdb6.plan.md
@@ -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 花括号"]
+```
+
+
+
diff --git a/.cursor/plans/字段提示括号扩展_91441aee.plan.md b/.cursor/plans/字段提示括号扩展_91441aee.plan.md
new file mode 100644
index 0000000..1730c19
--- /dev/null
+++ b/.cursor/plans/字段提示括号扩展_91441aee.plan.md
@@ -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
+{{ column.ps }}
+```
+
+样式为灰色小字,紧贴输入框右侧。
+
+### 2. Table.vue — 列头 tooltip
+
+给 `el-table-column` 加 header 插槽,当 `td.ps` 存在时用 `el-tooltip` 包裹列头文字:
+
+```html
+
+
+ {{ td.label }}
+
+
+```
+
+无 `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 是否需要手动清理。需要先确认具体是哪些列出现了这个问题。
\ No newline at end of file
diff --git a/code/config.go b/code/config.go
index 0cdb962..3406f3b 100644
--- a/code/config.go
+++ b/code/config.go
@@ -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 {
diff --git a/code/makecode.go b/code/makecode.go
index daa0af9..637e883 100644
--- a/code/makecode.go
+++ b/code/makecode.go
@@ -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
diff --git a/db/identifier.go b/db/identifier.go
index 9344dea..3211e58 100644
--- a/db/identifier.go
+++ b/db/identifier.go
@@ -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 去除标识符两端的引号(反引号或双引号)
diff --git a/docs/CodeGen_使用说明.md b/docs/CodeGen_使用说明.md
index 771ab1a..02fcd3f 100644
--- a/docs/CodeGen_使用说明.md
+++ b/docs/CodeGen_使用说明.md
@@ -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` 字段在前端的展示效果:
+- 编辑/新增页面:输入框右侧灰色小字提示
+- 表格/详情页面:鼠标悬停字段名时气泡提示
+
---
## 自定义规则
diff --git a/docs/DatabaseDesign_数据库设计规范.md b/docs/DatabaseDesign_数据库设计规范.md
index b88a491..8bd34ce 100644
--- a/docs/DatabaseDesign_数据库设计规范.md
+++ b/docs/DatabaseDesign_数据库设计规范.md
@@ -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 字段备注