diff --git a/server/internal/library/hggen/views/column_map.go b/server/internal/library/hggen/views/column_map.go index c5c4590..1cbdfda 100644 --- a/server/internal/library/hggen/views/column_map.go +++ b/server/internal/library/hggen/views/column_map.go @@ -100,6 +100,7 @@ const ( FormModeInputNumber = "InputNumber" // 数字输入 FormModeInputTextarea = "InputTextarea" // 文本域 FormModeInputEditor = "InputEditor" // 富文本 + FormModeInputYaml = "InputYaml" // YAML配置 FormModeInputDynamic = "InputDynamic" // 动态键值对 FormModeDate = "Date" // 日期选择(Y-M-D) FormModeDateRange = "DateRange" // 日期范围选择 @@ -122,7 +123,7 @@ const ( ) var FormModes = []string{ - FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputDynamic, + FormModeInput, FormModeInputNumber, FormModeInputTextarea, FormModeInputEditor, FormModeInputYaml, FormModeInputDynamic, FormModeDate, FormModeDateRange, FormModeTime, FormModeTimeRange, FormModeRadio, FormModeCheckbox, FormModeSelect, FormModeSelectMultiple, FormModeTreeSelect, FormModeCascader, FormModeUploadImage, FormModeUploadImages, FormModeUploadFile, FormModeUploadFiles, @@ -136,6 +137,7 @@ var FormModeMap = map[string]string{ FormModeInputNumber: "数字输入", FormModeInputTextarea: "文本域", FormModeInputEditor: "富文本", + FormModeInputYaml: "YAML配置", FormModeInputDynamic: "动态键值对", FormModeDate: "日期选择(Y-M-D)", FormModeDateRange: "日期范围选择", @@ -173,6 +175,7 @@ const ( FormRoleAccount = "account" FormRolePassword = "password" FormRoleAmount = "amount" + FormRoleYaml = "yaml" ) var FormRoleMap = map[string]string{ @@ -191,6 +194,7 @@ var FormRoleMap = map[string]string{ FormRoleAccount: "账号", FormRolePassword: "密码", FormRoleAmount: "金额", + FormRoleYaml: "YAML格式", } // 查询条件 diff --git a/server/internal/library/hggen/views/curd_generate_input.go b/server/internal/library/hggen/views/curd_generate_input.go index 2e94944..b82ac3a 100644 --- a/server/internal/library/hggen/views/curd_generate_input.go +++ b/server/internal/library/hggen/views/curd_generate_input.go @@ -30,6 +30,7 @@ const ( InputTypeInsertFields = 6 // 编辑新增过滤字段 InputTypeTreeOptionFields = 7 // 关系树查询字段 EditInpValidatorGenerally = "if err := g.Validator().Rules(\"%s\").Data(in.%s).Messages(\"%s\").Run(ctx); err != nil {\n\t\treturn err.Current()\n\t}\n" + EditInpValidatorYaml = "if err := validate.ValidateYAML(in.%s); err != nil {\n\t\treturn gerror.Newf(\"%s必须为有效的YAML格式: %%s\", err.Error())\n\t}\n" ) func (l *gCurd) inputTplData(ctx context.Context, in *CurdPreviewInput) (data g.Map, err error) { @@ -308,6 +309,8 @@ func makeValidatorFunc(field *sysin.GenCodesColumnListModel) (err error, rule st rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$", field.GoName, field.Dc+"必须包含6-18为字母和数字") } else if field.FormRole == FormRoleAmount { rule = fmt.Sprintf(EditInpValidatorGenerally, "regex:(^[0-9]{1,10}$)|(^[0-9]{1,10}[\\\\.]{1}[0-9]{1,2}$)", field.GoName, field.Dc+"最多允许输入10位整数及2位小数") + } else if field.FormRole == FormRoleYaml { + rule = fmt.Sprintf(EditInpValidatorYaml, field.GoName, field.Dc) } else { err = gerror.New("not support") } diff --git a/server/internal/library/hggen/views/curd_generate_logic.go b/server/internal/library/hggen/views/curd_generate_logic.go index 9a0d09c..4bc2028 100644 --- a/server/internal/library/hggen/views/curd_generate_logic.go +++ b/server/internal/library/hggen/views/curd_generate_logic.go @@ -99,10 +99,11 @@ func (l *gCurd) generateLogicSwitchFields(ctx context.Context, in *CurdPreviewIn func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.Map { var ( - data = make(g.Map) - updateBuffer = bytes.NewBuffer(nil) - insertBuffer = bytes.NewBuffer(nil) - uniqueBuffer = bytes.NewBuffer(nil) + data = make(g.Map) + updateBuffer = bytes.NewBuffer(nil) + insertBuffer = bytes.NewBuffer(nil) + uniqueBuffer = bytes.NewBuffer(nil) + validationBuffer = bytes.NewBuffer(nil) ) for _, field := range in.masterFields { @@ -117,6 +118,11 @@ func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.M if field.Unique { uniqueBuffer.WriteString(fmt.Sprintf(LogicEditUnique, field.GoName, in.In.DaoName, in.In.DaoName, field.GoName, field.GoName, field.Dc,in.pk.GoName)) } + + // 添加 YAML 格式验证 + if field.IsEdit && field.FormRole == FormRoleYaml { + validationBuffer.WriteString(fmt.Sprintf(EditInpValidatorYaml, field.GoName, field.Dc)) + } } notFilterAuth := "" @@ -130,6 +136,7 @@ func (l *gCurd) generateLogicEdit(ctx context.Context, in *CurdPreviewInput) g.M data["update"] = updateBuffer.String() data["insert"] = insertBuffer.String() data["unique"] = uniqueBuffer.String() + data["validation"] = validationBuffer.String() return data } diff --git a/server/internal/library/hggen/views/curd_generate_web_edit.go b/server/internal/library/hggen/views/curd_generate_web_edit.go index 2bb771c..970796b 100644 --- a/server/internal/library/hggen/views/curd_generate_web_edit.go +++ b/server/internal/library/hggen/views/curd_generate_web_edit.go @@ -53,6 +53,9 @@ func (l *gCurd) generateWebEditFormItem(ctx context.Context, in *CurdPreviewInpu case FormModeInputEditor: component = fmt.Sprintf("\n \n ", field.Dc, field.TsName, field.TsName, field.TsName) + case FormModeInputYaml: + component = fmt.Sprintf("\n handleYamlValidationChange(isValid, '%s')\" />\n ", field.Dc, field.TsName, field.TsName, field.TsName, field.Dc, field.TsName) + case FormModeInputDynamic: component = fmt.Sprintf("\n \n ", field.Dc, field.TsName, field.TsName) @@ -150,6 +153,7 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) data = make(g.Map) importBuffer = bytes.NewBuffer(nil) setupBuffer = bytes.NewBuffer(nil) + hasYamlField = false ) importBuffer.WriteString(" import { ref, computed } from 'vue';\n") @@ -190,6 +194,11 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) if !gstr.Contains(importBuffer.String(), `import Editor`) { importBuffer.WriteString(" import Editor from '@/components/Editor/editor.vue';\n") } + case FormModeInputYaml: + if !gstr.Contains(importBuffer.String(), `import YamlEditor`) { + importBuffer.WriteString(" import YamlEditor from '@/components/YamlEditor/index.vue';\n") + } + hasYamlField = true case FormModeUploadImage, FormModeUploadImages: if !gstr.Contains(importBuffer.String(), `import UploadImage`) { importBuffer.WriteString(" import UploadImage from '@/components/Upload/uploadImage.vue';\n") @@ -207,6 +216,13 @@ func (l *gCurd) generateWebEditScript(ctx context.Context, in *CurdPreviewInput) } } + // 根据是否有 YAML 字段添加相应的验证逻辑 + if hasYamlField { + setupBuffer.WriteString(" const yamlValidationStates = ref(new Map());\n const isFormValid = computed(() => {\n for (const [fieldName, isValid] of yamlValidationStates.value) {\n if (!isValid) return false;\n }\n return true;\n });\n const handleYamlError = (error) => {\n console.error('YAML 验证错误:', error);\n };\n const handleYamlValid = (content) => {\n console.log('YAML 验证通过:', content);\n };\n const handleYamlValidationChange = (isValid, fieldName) => {\n yamlValidationStates.value.set(fieldName, isValid);\n };\n") + } else { + setupBuffer.WriteString(" const isFormValid = ref(true);\n") + } + data["import"] = importBuffer.String() data["setup"] = setupBuffer.String() return data diff --git a/server/internal/library/hggen/views/curd_generate_web_model.go b/server/internal/library/hggen/views/curd_generate_web_model.go index ea444a8..2bbede3 100644 --- a/server/internal/library/hggen/views/curd_generate_web_model.go +++ b/server/internal/library/hggen/views/curd_generate_web_model.go @@ -309,7 +309,7 @@ func (l *gCurd) generateWebModelFormSchemaEach(buffer *bytes.Buffer, fields []*s // 这里根据编辑表单组件来进行推断,如果没有则使用默认input,这可能会导致和查询条件所需参数不符的情况 switch field.FormMode { - case FormModeInput, FormModeInputTextarea, FormModeInputEditor: + case FormModeInput, FormModeInputTextarea, FormModeInputEditor, FormModeInputYaml: component = defaultComponent case FormModeInputNumber: diff --git a/server/internal/library/hggen/views/curd_generate_web_view.go b/server/internal/library/hggen/views/curd_generate_web_view.go index 304ab32..7e27fdc 100644 --- a/server/internal/library/hggen/views/curd_generate_web_view.go +++ b/server/internal/library/hggen/views/curd_generate_web_view.go @@ -34,6 +34,9 @@ func (l *gCurd) generateWebViewItem(ctx context.Context, in *CurdPreviewInput) s case FormModeInputTextarea, FormModeInputEditor: component = fmt.Sprintf("\n \n ", field.Dc, field.TsName) + case FormModeInputYaml: + component = fmt.Sprintf("\n \n
{{ formValue.%s }}
", field.Dc, field.TsName) + case FormModeInputDynamic: component = defaultComponent diff --git a/server/internal/library/validate/yaml.go b/server/internal/library/validate/yaml.go new file mode 100644 index 0000000..6ef4f9e --- /dev/null +++ b/server/internal/library/validate/yaml.go @@ -0,0 +1,50 @@ +// Package validate +// @Link https://github.com/bufanyun/hotgo +// @Copyright Copyright (c) 2025 HotGo CLI +// @Author Ms <133814250@qq.com> +// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE +package validate + +import ( + "strings" + + "gopkg.in/yaml.v3" +) + +// IsValidYAML 验证字符串是否为有效的YAML格式 +func IsValidYAML(yamlStr string) bool { + if strings.TrimSpace(yamlStr) == "" { + return true // 空字符串被认为是有效的 + } + + var temp interface{} + err := yaml.Unmarshal([]byte(yamlStr), &temp) + return err == nil +} + +// ValidateYAML 验证YAML格式并返回错误信息 +func ValidateYAML(yamlStr string) error { + if strings.TrimSpace(yamlStr) == "" { + return nil // 空字符串被认为是有效的 + } + + var temp interface{} + err := yaml.Unmarshal([]byte(yamlStr), &temp) + return err +} + +// ParseYAML 解析YAML字符串为interface{} +func ParseYAML(yamlStr string) (interface{}, error) { + var result interface{} + err := yaml.Unmarshal([]byte(yamlStr), &result) + return result, err +} + +// ToYAML 将interface{}转换为YAML字符串 +func ToYAML(data interface{}) (string, error) { + bytes, err := yaml.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/server/resource/generate/default/curd/logic.go.template b/server/resource/generate/default/curd/logic.go.template index 8cf4ad4..b9dc63a 100644 --- a/server/resource/generate/default/curd/logic.go.template +++ b/server/resource/generate/default/curd/logic.go.template @@ -123,7 +123,7 @@ func (s *s@{.servFunName}) Export(ctx context.Context, in *@{.templateGroup}in.@ @{ if eq .options.Step.HasEdit true } // Edit 修改/新增@{.tableComment} func (s *s@{.servFunName}) Edit(ctx context.Context, in *@{.templateGroup}in.@{.varName}EditInp) (err error) { -@{.edit.unique} return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { +@{.edit.unique}@{.edit.validation} return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) { @{ if eq .options.Step.IsTreeTable true }in.Pid, in.Level, in.Tree, err = hgorm.AutoUpdateTree(ctx, &dao.@{.daoName}, in.@{.pk.GoName}, in.Pid) if err != nil { return err diff --git a/server/resource/generate/default/curd/web.edit.vue.template b/server/resource/generate/default/curd/web.edit.vue.template index dc5c505..de67ce4 100644 --- a/server/resource/generate/default/curd/web.edit.vue.template +++ b/server/resource/generate/default/curd/web.edit.vue.template @@ -30,7 +30,7 @@ @@ -54,7 +54,7 @@ const dialogWidth = computed(() => { return adaModalWidth(840); }); - +@{.script.setup} // 提交表单 function confirmForm(e) { e.preventDefault(); diff --git a/web/package.json b/web/package.json index 43253ac..12bfbe0 100644 --- a/web/package.json +++ b/web/package.json @@ -29,6 +29,11 @@ "test prod gzip": "http-server dist --cors --gzip -c-1" }, "dependencies": { + "@codemirror/basic-setup": "^0.20.0", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/state": "^6.5.2", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.2", "@vicons/antd": "^0.12.0", "@vicons/ionicons5": "^0.12.0", "@vue/runtime-core": "^3.4.38", @@ -36,11 +41,13 @@ "@vueuse/core": "^11.0.3", "axios": "^1.7.7", "blueimp-md5": "^2.19.0", + "codemirror": "^6.0.2", "date-fns": "^3.6.0", "echarts": "^5.5.1", "element-resize-detector": "^1.2.4", "fingerprintjs2": "^2.1.4", "highlight.js": "^11.10.0", + "js-yaml": "^4.1.0", "lodash-es": "^4.17.21", "mint-filter": "^4.0.3", "mitt": "^3.0.1", @@ -68,6 +75,7 @@ "@commitlint/config-conventional": "^19.4.1", "@eslint/eslintrc": "^3.1.0", "@types/fs-extra": "^11.0.4", + "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", "@types/node": "^22.5.2", "@typescript-eslint/eslint-plugin": "^8.4.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 7e4f195..ec069a7 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,6 +8,21 @@ importers: .: dependencies: + '@codemirror/basic-setup': + specifier: ^0.20.0 + version: 0.20.0 + '@codemirror/lang-yaml': + specifier: ^6.1.2 + version: 6.1.2 + '@codemirror/state': + specifier: ^6.5.2 + version: 6.5.2 + '@codemirror/theme-one-dark': + specifier: ^6.1.3 + version: 6.1.3 + '@codemirror/view': + specifier: ^6.38.2 + version: 6.38.2 '@vicons/antd': specifier: ^0.12.0 version: 0.12.0 @@ -29,6 +44,9 @@ importers: blueimp-md5: specifier: ^2.19.0 version: 2.19.0 + codemirror: + specifier: ^6.0.2 + version: 6.0.2 date-fns: specifier: ^3.6.0 version: 3.6.0 @@ -44,6 +62,9 @@ importers: highlight.js: specifier: ^11.10.0 version: 11.10.0 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -120,6 +141,9 @@ importers: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 '@types/lodash-es': specifier: ^4.17.12 version: 4.17.12 @@ -503,6 +527,58 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@codemirror/autocomplete@0.20.3': + resolution: {integrity: sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==} + + '@codemirror/autocomplete@6.18.7': + resolution: {integrity: sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==} + + '@codemirror/basic-setup@0.20.0': + resolution: {integrity: sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==} + deprecated: In version 6.0, this package has been renamed to just 'codemirror' + + '@codemirror/commands@0.20.0': + resolution: {integrity: sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==} + + '@codemirror/commands@6.8.1': + resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==} + + '@codemirror/lang-yaml@6.1.2': + resolution: {integrity: sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==} + + '@codemirror/language@0.20.2': + resolution: {integrity: sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==} + + '@codemirror/language@6.11.3': + resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + + '@codemirror/lint@0.20.3': + resolution: {integrity: sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==} + + '@codemirror/lint@6.8.5': + resolution: {integrity: sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==} + + '@codemirror/search@0.20.1': + resolution: {integrity: sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==} + + '@codemirror/search@6.5.11': + resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==} + + '@codemirror/state@0.20.1': + resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==} + + '@codemirror/state@6.5.2': + resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + + '@codemirror/theme-one-dark@6.1.3': + resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} + + '@codemirror/view@0.20.7': + resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==} + + '@codemirror/view@6.38.2': + resolution: {integrity: sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==} + '@commitlint/cli@19.4.1': resolution: {integrity: sha512-EerFVII3ZcnhXsDT9VePyIdCJoh3jEzygN1L37MjQXgPfGS6fJTWL/KHClVMod1d8w94lFC3l4Vh/y5ysVAz2A==} engines: {node: '>=v18'} @@ -1036,6 +1112,30 @@ packages: '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + '@lezer/common@0.16.1': + resolution: {integrity: sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==} + + '@lezer/common@1.2.3': + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + + '@lezer/highlight@0.16.0': + resolution: {integrity: sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==} + + '@lezer/highlight@1.2.1': + resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} + + '@lezer/lr@0.16.3': + resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lezer/yaml@1.0.3': + resolution: {integrity: sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==} + + '@marijn/find-cluster-break@1.0.2': + resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1287,6 +1387,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1899,6 +2002,9 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + codemirror@6.0.2: + resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} + collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} @@ -2028,6 +2134,9 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -4138,6 +4247,9 @@ packages: resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} engines: {node: '>=0.10.0'} + style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + stylelint-config-prettier@9.0.5: resolution: {integrity: sha512-U44lELgLZhbAD/xy/vncZ2Pq8sh2TnpiPvo38Ifg9+zeioR+LAkHu0i6YORIOxFafZoVg0xqQwex6e6F25S5XA==} engines: {node: '>= 12'} @@ -4555,6 +4667,9 @@ packages: peerDependencies: vue: ^3.0.11 + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -4931,6 +5046,122 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@codemirror/autocomplete@0.20.3': + dependencies: + '@codemirror/language': 0.20.2 + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + '@lezer/common': 0.16.1 + + '@codemirror/autocomplete@6.18.7': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/common': 1.2.3 + + '@codemirror/basic-setup@0.20.0': + dependencies: + '@codemirror/autocomplete': 0.20.3 + '@codemirror/commands': 0.20.0 + '@codemirror/language': 0.20.2 + '@codemirror/lint': 0.20.3 + '@codemirror/search': 0.20.1 + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + + '@codemirror/commands@0.20.0': + dependencies: + '@codemirror/language': 0.20.2 + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + '@lezer/common': 0.16.1 + + '@codemirror/commands@6.8.1': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/common': 1.2.3 + + '@codemirror/lang-yaml@6.1.2': + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + '@lezer/yaml': 1.0.3 + + '@codemirror/language@0.20.2': + dependencies: + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + '@lezer/common': 0.16.1 + '@lezer/highlight': 0.16.0 + '@lezer/lr': 0.16.3 + style-mod: 4.1.2 + + '@codemirror/language@6.11.3': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + style-mod: 4.1.2 + + '@codemirror/lint@0.20.3': + dependencies: + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + crelt: 1.0.6 + + '@codemirror/lint@6.8.5': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + crelt: 1.0.6 + + '@codemirror/search@0.20.1': + dependencies: + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + crelt: 1.0.6 + + '@codemirror/search@6.5.11': + dependencies: + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + crelt: 1.0.6 + + '@codemirror/state@0.20.1': {} + + '@codemirror/state@6.5.2': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/theme-one-dark@6.1.3': + dependencies: + '@codemirror/language': 6.11.3 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + '@lezer/highlight': 1.2.1 + + '@codemirror/view@0.20.7': + dependencies: + '@codemirror/state': 0.20.1 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + + '@codemirror/view@6.38.2': + dependencies: + '@codemirror/state': 6.5.2 + crelt: 1.0.6 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + '@commitlint/cli@19.4.1(@types/node@22.5.2)(typescript@5.5.4)': dependencies: '@commitlint/format': 19.3.0 @@ -5463,6 +5694,34 @@ snapshots: '@juggle/resize-observer@3.4.0': {} + '@lezer/common@0.16.1': {} + + '@lezer/common@1.2.3': {} + + '@lezer/highlight@0.16.0': + dependencies: + '@lezer/common': 0.16.1 + + '@lezer/highlight@1.2.1': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/lr@0.16.3': + dependencies: + '@lezer/common': 0.16.1 + + '@lezer/lr@1.4.2': + dependencies: + '@lezer/common': 1.2.3 + + '@lezer/yaml@1.0.3': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/highlight': 1.2.1 + '@lezer/lr': 1.4.2 + + '@marijn/find-cluster-break@1.0.2': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5665,6 +5924,8 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': optional: true @@ -6451,6 +6712,16 @@ snapshots: co@4.6.0: {} + codemirror@6.0.2: + dependencies: + '@codemirror/autocomplete': 6.18.7 + '@codemirror/commands': 6.8.1 + '@codemirror/language': 6.11.3 + '@codemirror/lint': 6.8.5 + '@codemirror/search': 6.5.11 + '@codemirror/state': 6.5.2 + '@codemirror/view': 6.38.2 + collect-v8-coverage@1.0.2: {} color-convert@1.9.3: @@ -6594,6 +6865,8 @@ snapshots: create-require@1.1.1: optional: true + crelt@1.0.6: {} + cross-env@7.0.3: dependencies: cross-spawn: 7.0.3 @@ -8909,6 +9182,8 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 + style-mod@4.1.2: {} + stylelint-config-prettier@9.0.5(stylelint@16.9.0(typescript@5.5.4)): dependencies: stylelint: 16.9.0(typescript@5.5.4) @@ -9403,6 +9678,8 @@ snapshots: vooks: 0.2.12(vue@3.4.38(typescript@5.5.4)) vue: 3.4.38(typescript@5.5.4) + w3c-keyname@2.2.8: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 diff --git a/web/src/components/YamlEditor/index.vue b/web/src/components/YamlEditor/index.vue new file mode 100644 index 0000000..9075f62 --- /dev/null +++ b/web/src/components/YamlEditor/index.vue @@ -0,0 +1,445 @@ + + + + + \ No newline at end of file diff --git a/web/src/utils/validateUtil.ts b/web/src/utils/validateUtil.ts index 5bbe50f..dbbcb42 100644 --- a/web/src/utils/validateUtil.ts +++ b/web/src/utils/validateUtil.ts @@ -1,4 +1,5 @@ import { FormItemRule } from 'naive-ui'; +import * as yaml from 'js-yaml'; /** * @description 表单验证封装 */ @@ -221,6 +222,27 @@ export const validate = { } return true; }, + // YAML格式验证 + yaml(rule: FormItemRule, value: any, callback: Function): boolean | Error { + if (!value && !rule.required) { + callback(); + return true; + } + if (!value) { + callback(new Error('请输入YAML配置')); + return false; + } + + try { + // 使用 js-yaml 进行同步验证 + yaml.load(value); + callback(); + return true; + } catch (error: any) { + callback(new Error(`YAML格式错误: ${error.message}`)); + return false; + } + }, // 不验证 none(_rule: FormItemRule, _value: any, callback: Function): boolean { callback();