diff --git a/frontend/src/components/QueryEditor.external-sql-save.test.tsx b/frontend/src/components/QueryEditor.external-sql-save.test.tsx index c725e11..bb9f173 100644 --- a/frontend/src/components/QueryEditor.external-sql-save.test.tsx +++ b/frontend/src/components/QueryEditor.external-sql-save.test.tsx @@ -4367,7 +4367,14 @@ describe('QueryEditor external SQL save', () => { const modalSource = readFileSync(new URL('./SnippetSettingsModal.tsx', import.meta.url), 'utf8'); const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8'); - expect(modalSource).toContain('片段语法说明(可选)'); + expect(modalSource).toContain('片段语法说明(可编辑)'); + expect(modalSource).toContain('data-sql-snippet-syntax-help-editor="true"'); + expect(modalSource).toContain("defaultActiveKey={['snippet-help']}"); + expect(modalSource).toContain('footer={null}'); + expect(modalSource).toContain('data-sql-snippet-action-row="true"'); + expect(modalSource).toContain('body: { paddingTop: 8, paddingBottom: 24 }'); + expect(modalSource).toContain("size=\"large\""); + expect(modalSource).toContain('minWidth: 96'); expect(modalSource).toContain('syntaxHelp'); expect(modalSource).toContain('占位符语法参考'); expect(source).toContain('s.syntaxHelp || s.description || s.body'); diff --git a/frontend/src/components/SnippetSettingsModal.tsx b/frontend/src/components/SnippetSettingsModal.tsx index 1b8faf2..41b3ba3 100644 --- a/frontend/src/components/SnippetSettingsModal.tsx +++ b/frontend/src/components/SnippetSettingsModal.tsx @@ -159,29 +159,51 @@ export default function SnippetSettingsModal({ [resetBuiltinSqlSnippet, selectedId], ); - const syntaxHelpItems = [ - { - key: 'syntax', - label: '占位符语法参考', - children: ( -
-
{'${1:占位符} 第一个 Tab 位,占位符为提示文字'}
-
{'${2:默认值} 第二个 Tab 位,默认值可直接确认'}
-
{'$0 最终光标位置'}
-
{'${1:表名} 同一数字在多处出现时会同步编辑'}
-
{'内置变量(展开时自动替换为实际值):'}
-
{'${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} 当前日期'}
-
{'${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND} 当前时间'}
-
{'${CURRENT_SECONDS_UNIX} Unix 时间戳'}
-
{'${UUID} 随机 UUID'}
-
{'${RANDOM} 6 位随机数'}
-
- {'示例:SELECT ${1:列名} FROM ${2:表名} WHERE date >= \'${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}\';$0'} + const syntaxHelpItems = useMemo( + () => [ + { + key: 'snippet-help', + label: '片段语法说明(可编辑)', + children: ( + setDraft((d) => ({ ...d, syntaxHelp: e.target.value }))} + placeholder="展示在补全详情中的用法说明,例如占位符含义、参数约定或注意事项" + maxLength={1000} + autoSize={{ minRows: 4, maxRows: 8 }} + style={{ + fontSize: 12, + resize: 'none', + fontFamily: 'var(--gn-font-mono)', + }} + /> + ), + }, + { + key: 'syntax', + label: '占位符语法参考', + children: ( +
+
{'${1:占位符} 第一个 Tab 位,占位符为提示文字'}
+
{'${2:默认值} 第二个 Tab 位,默认值可直接确认'}
+
{'$0 最终光标位置'}
+
{'${1:表名} 同一数字在多处出现时会同步编辑'}
+
{'内置变量(展开时自动替换为实际值):'}
+
{'${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} 当前日期'}
+
{'${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND} 当前时间'}
+
{'${CURRENT_SECONDS_UNIX} Unix 时间戳'}
+
{'${UUID} 随机 UUID'}
+
{'${RANDOM} 6 位随机数'}
+
+ {'示例:SELECT ${1:列名} FROM ${2:表名} WHERE date >= \'${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}\';$0'} +
-
- ), - }, - ]; + ), + }, + ], + [draft.syntaxHelp, mutedColor, textColor], + ); const showEditor = isCreating || selectedSnippet; @@ -217,14 +239,9 @@ export default function SnippetSettingsModal({ styles={{ content: shellStyle, header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 }, - body: { paddingTop: 8 }, - footer: { background: 'transparent', borderTop: 'none', paddingTop: 40 }, + body: { paddingTop: 8, paddingBottom: 24 }, }} - footer={[ - , - ]} + footer={null} >
{/* Left: snippet list */} @@ -353,18 +370,6 @@ export default function SnippetSettingsModal({ />
-
-
片段语法说明(可选)
- setDraft((d) => ({ ...d, syntaxHelp: e.target.value }))} - placeholder="展示在补全详情中的用法说明,例如占位符含义、参数约定或注意事项" - maxLength={1000} - autoSize={{ minRows: 2, maxRows: 4 }} - style={{ fontSize: 12, resize: 'none' }} - /> -
-
片段内容
-
- {draft.isBuiltin && draft.createdAt && ( - handleReset(draft.id)} - > - - - )} - {!draft.isBuiltin && !isCreating && ( - handleDelete(draft.id)} - > - - - )} - -
) : (
+
+ {showEditor && draft.isBuiltin && draft.createdAt && ( + handleReset(draft.id)} + > + + + )} + {showEditor && !draft.isBuiltin && !isCreating && ( + handleDelete(draft.id)} + > + + + )} + {showEditor && ( + + )} + +
); } diff --git a/frontend/src/store.test.ts b/frontend/src/store.test.ts index 5fc2344..4b1a3be 100644 --- a/frontend/src/store.test.ts +++ b/frontend/src/store.test.ts @@ -1195,4 +1195,41 @@ describe('store appearance persistence', () => { windows: { combo: 'Enter', enabled: true }, }); }); + + it('updates an existing custom SQL snippet by id and persists editable syntax help', async () => { + const { useStore } = await importStore(); + const original = { + id: 'custom-merge', + prefix: 'mrg', + name: 'MERGE INTO', + description: 'Oracle merge 模板', + syntaxHelp: '旧说明', + body: 'MERGE INTO t USING s ON (t.id = s.id)$0', + isBuiltin: false, + createdAt: 1710000000000, + }; + + useStore.getState().saveSqlSnippet(original); + useStore.getState().saveSqlSnippet({ + ...original, + name: 'MERGE INTO 更新', + syntaxHelp: '新说明:目标表、数据源、关联字段均可修改', + body: 'MERGE INTO ${1:目标表} t USING ${2:源表} s ON (${3:关联条件})$0', + }); + + const snippets = useStore.getState().sqlSnippets.filter((s) => s.id === original.id); + expect(snippets).toHaveLength(1); + expect(snippets[0]).toMatchObject({ + prefix: 'mrg', + name: 'MERGE INTO 更新', + syntaxHelp: '新说明:目标表、数据源、关联字段均可修改', + body: 'MERGE INTO ${1:目标表} t USING ${2:源表} s ON (${3:关联条件})$0', + isBuiltin: false, + }); + + const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}'); + const persistedSnippets = persisted.state.sqlSnippets.filter((s: { id: string }) => s.id === original.id); + expect(persistedSnippets).toHaveLength(1); + expect(persistedSnippets[0].syntaxHelp).toBe('新说明:目标表、数据源、关联字段均可修改'); + }); });