diff --git a/src/dashboard-front/.eslintignore b/src/dashboard-front/.eslintignore index 1a3d7d028..fa2f7ddf0 100644 --- a/src/dashboard-front/.eslintignore +++ b/src/dashboard-front/.eslintignore @@ -12,3 +12,4 @@ src/views/permission/apply/index.vue src/assets/** *bak* src/views/resource/setting/detail.vue +src/views/resource/setting/import.vue diff --git a/src/dashboard-front/package.json b/src/dashboard-front/package.json index 7d997804d..6e85ed7c0 100644 --- a/src/dashboard-front/package.json +++ b/src/dashboard-front/package.json @@ -41,6 +41,7 @@ "highlight.js": "^11.9.0", "js-cookie": "^3.0.5", "js-yaml": "^4.1.0", + "jsonpath-plus": "^9.0.0", "lodash": "^4.17.21", "markdown-it": "^13.0.2", "mavon-editor": "^3.0.1", diff --git a/src/dashboard-front/src/components/ag-editor.vue b/src/dashboard-front/src/components/ag-editor.vue index 8e267778b..086958dad 100644 --- a/src/dashboard-front/src/components/ag-editor.vue +++ b/src/dashboard-front/src/components/ag-editor.vue @@ -11,6 +11,14 @@ import * as monaco from 'monaco-editor'; let editor = null; // 编辑器实例 const monacoEditor = ref(null); +// 编辑器装饰器(高亮效果等) +let decorations = []; +// 可切换字号范围 +const fontSizeOptions = [14, 20, 24]; +// 可切换行高范围 +const lineHeight = [24, 30, 34]; +// 当前选中字号的index +const currentFontSizeIndex = ref(0); // 定义从父组件接收的属性 const props = defineProps({ modelValue: { type: [String, Object, Array], default: () => 'yaml' }, @@ -23,7 +31,7 @@ const props = defineProps({ const { modelValue, language, readOnly, width, height, theme } = toRefs(props); -const emit = defineEmits(['change', 'update:modelValue']); +const emit = defineEmits(['change', 'update:modelValue', 'findStateChanged']); // 挂载 onMounted(() => { @@ -86,9 +94,12 @@ const initEditor = () => { lineNumbersMinChars: 5, // 行号最小字符 number readOnly: readOnly.value, // 是否只读 取值 true | false lineHeight: 24, + glyphMargin: true, // 是否显示行号左侧装饰,用于显示当前行的错误信息等级:error | warning }); editorMounted(); // 编辑器初始化后 + // 初始化编辑器装饰 + decorations = editor.createDecorationsCollection([]); }; const editorMounted = () => { @@ -96,6 +107,15 @@ const editorMounted = () => { const yamlValue = getValue(); emitChange(yamlValue, event); }); + + // 监听搜索工具状态变化,把可视状态传递出去 + editor.getContribution('editor.contrib.findController') + .getState() + .onFindReplaceStateChange(() => { + const isVisible = editor.getContribution('editor.contrib.findController') + .getState().isRevealed; + emit('findStateChanged', isVisible); + }); }; // 修改editor的值 @@ -104,15 +124,82 @@ const emitChange = (emitValue, event) => { emit('update:modelValue', emitValue, event); }; +// 更改光标位置 +const setCursorPos = ({ lineNumber }) => { + const model = editor.getModel(); + + if (!model) return; + + const lastColumnNumber = model.getLineLastNonWhitespaceColumn(lineNumber); + editor.focus(); + editor.setPosition(new monaco.Position(lineNumber, lastColumnNumber)); + editor.revealLineInCenter(lineNumber); +}; + +const genLineDecorations = (decorationOptions) => { + const decoOptions = decorationOptions.filter(o => o.position) + .map(o => ({ + range: { + startLineNumber: o.position.lineNumber, + endLineNumber: o.position.lineNumber, + startColumn: o.position.column, + endColumn: o.position.column, + }, + options: { + isWholeLine: true, // 整行高亮 + className: + `lineHighlight${o.level}`, // 当前行装饰用类名 + glyphMarginClassName: + `glyphMargin${o.level}`, // 当前行左侧装饰(glyph)用类名 + }, + })); + decorations = editor.createDecorationsCollection(decoOptions); +}; + +const setDecorations = () => { + decorations.set(); +}; + +const clearDecorations = () => { + decorations.clear(); +}; + +const getModel = () => editor.getModel(); + +const showFindPanel = () => { + editor.trigger('', 'actions.find'); +}; + +const closeFindPanel = () => { + editor.trigger('', 'closeFindWidget'); +}; + +const switchFontSize = () => { + currentFontSizeIndex.value = (currentFontSizeIndex.value + 1) % fontSizeOptions.length; + editor.updateOptions({ + fontSize: fontSizeOptions[currentFontSizeIndex.value], + lineHeight: lineHeight[currentFontSizeIndex.value], + }); +}; + defineExpose({ setValue, + setCursorPos, + setDecorations, + clearDecorations, + genLineDecorations, + getModel, + getValue, + showFindPanel, + closeFindPanel, + switchFontSize, }); diff --git a/src/dashboard-front/src/images/font.svg b/src/dashboard-front/src/images/font.svg new file mode 100644 index 000000000..aae392b64 --- /dev/null +++ b/src/dashboard-front/src/images/font.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/dashboard-front/src/language/lang.ts b/src/dashboard-front/src/language/lang.ts index 3e2bc3958..b1a6b97a3 100644 --- a/src/dashboard-front/src/language/lang.ts +++ b/src/dashboard-front/src/language/lang.ts @@ -639,6 +639,7 @@ const lang: ILANG = { '上传文件': ['Upload'], '导入 Swagger 文件': ['Import Swagger'], 'Swagger 说明文档': ['Swagger Doc'], + '使用指引': ['Usage Doc'], '模板示例': ['Template Example'], '请确认以下文档变更:': ['Confirm the following document changes:'], '导入文档压缩包': ['Import Archive'], @@ -953,6 +954,7 @@ const lang: ILANG = { '应用将按网关申请全部驳回': ['Reject permission application for all resources under the gateway'], '申请应用:': ['App ID: '], '(json /yaml 格式)': ['(json /yaml format)'], + '支持 Swagger 2.0 和 OpenAPI 3.0 规范的文件,文件格式支持 JSON、YAML': ['Support Swagger 2.0 and OpenAPI 3.0 spec files, support JSON/YAML'], 'YAML格式': ['YAML Format'], 'JSON格式': ['JSON Format'], '您尚未创建中文文档': ['You have not created a Chinese document yet'], @@ -1481,6 +1483,36 @@ const lang: ILANG = { '确认发布 {version} 版本至 {stage} 环境?': ['Are you sure to release {version} to the {stage} environment?'], '发布后,将会覆盖原来的资源版本,请谨慎操作!': ['After the release, the original resource version will be overwritten, please exercise caution!'], '立即生成版本': ['Generate Version'], + '插件数量': ['Plugin Count'], + '不导入': ['Don\'t Import'], + '后端请求方法': ['Backend Method'], + '后端请求路径': ['Backend Path'], + '修改配置': ['Change Settings'], + '批量修改认证方式': ['Edit All Auth Config'], + '批量修改公开设置': ['Edit All Public Config'], + '恢复取消导入的资源': ['Undo Resources Uncheck'], + '恢复导入': ['Undo Uncheck'], + '请输入资源名称,按Enter搜索': ['Input Resource Name And Press Enter'], + '请输入资源名称/路径,按Enter搜索': ['Input Name/Path And Press Enter'], + '请确认导入的资源': ['Check Resources To Import'], + '确认导入资源?': ['Confirm To Import?'], + '语法校验': ['Validate Code'], + '校验通过': ['Validation Passed'], + '代码编辑器': ['Code Editor'], + '网关:': ['Gateway: '], + '将新增:': ['Creating '], + '条资源,更新覆盖': [' resource(s), Overwriting '], + '条资源': [' resource(s)'], + '个资源,新增': ['resource(s), creating'], + '个,更新': [', overwriting'], + '个,取消导入': [', not importing'], + '个': [' '], + '新增的资源(共{num}个)': ['Creating {num} Resources'], + '更新的资源(共{num}个)': ['Overwriting {num} Resources'], + '不导入的资源(共{num}个)': ['Not Importing {num} Resources'], + '生成新文档:原有的文档将会覆盖更新': ['Generate New Doc: Will Overwrite Old One'], + '校验文件': ['Validate File'], + '资源信息确认': ['Confirm Resources'], // 变量的使用 $t('test', { vari1: 1, vari2: 2 }) // // 变量的使用 $t('test', { vari1: 1, vari2: 2 }) diff --git a/src/dashboard-front/src/types/common.ts b/src/dashboard-front/src/types/common.ts index 96b09cadb..2778c4459 100644 --- a/src/dashboard-front/src/types/common.ts +++ b/src/dashboard-front/src/types/common.ts @@ -1,4 +1,6 @@ // 分页interface +import type { IPosition } from 'monaco-editor'; + export interface IPagination { small?: boolean offset: number @@ -41,3 +43,21 @@ export interface Staff { username: string; display_name: string; } + +// monaco editor 代码错误高亮要用的类型 +export type CodeErrorMsgType = 'All' | 'Error' | 'Warning'; + +export type ErrorReasonType = { + json_path: string, + paths: string[], + pathValue: any[], + quotedValue: string, + stringToFind: string, + message: string, + isDecorated: boolean, + level: CodeErrorMsgType, + offset: number, + position: IPosition | null, + regex?: RegExp | null, +}; + diff --git a/src/dashboard-front/src/views/resource/setting/comps/back-config.vue b/src/dashboard-front/src/views/resource/setting/comps/back-config.vue index d0e6d2364..a3717ee56 100644 --- a/src/dashboard-front/src/views/resource/setting/comps/back-config.vue +++ b/src/dashboard-front/src/views/resource/setting/comps/back-config.vue @@ -146,7 +146,7 @@ :label="t('请求类型')" > { if (Object.keys(val).length) { const { backend } = val; - backConfigData.value = { ...backend }; - handleServiceChange(backend.id); + backConfigData.value = { ...backConfigData.value, ...backend }; + if (backend?.id) { + handleServiceChange(backend.id); + } } }, { immediate: true }, diff --git a/src/dashboard-front/src/views/resource/setting/comps/base-info.vue b/src/dashboard-front/src/views/resource/setting/comps/base-info.vue index 022455673..5e397494d 100644 --- a/src/dashboard-front/src/views/resource/setting/comps/base-info.vue +++ b/src/dashboard-front/src/views/resource/setting/comps/base-info.vue @@ -147,8 +147,15 @@ watch( (val: any) => { if (Object.keys(val).length) { const { name, description, auth_config, is_public, allow_apply_permission, labels } = val; - const label_ids = labels.map((e: {id: number, name: string}) => e.id); - formData.value = { name: props.isClone ? `${name}_clone` : name, description, auth_config, is_public, allow_apply_permission, label_ids }; + const label_ids = labels.map((e: { id: number, name: string }) => e.id); + formData.value = { + name: props.isClone ? `${name}_clone` : name, + description, + auth_config: { ...auth_config }, + is_public, + allow_apply_permission, + label_ids, + }; resourcePermRequiredBackup.value = !!auth_config?.resource_perm_required; } }, diff --git a/src/dashboard-front/src/views/resource/setting/comps/edit-import-resource-side-slider.vue b/src/dashboard-front/src/views/resource/setting/comps/edit-import-resource-side-slider.vue new file mode 100644 index 000000000..0e08e1ac7 --- /dev/null +++ b/src/dashboard-front/src/views/resource/setting/comps/edit-import-resource-side-slider.vue @@ -0,0 +1,115 @@ + + + diff --git a/src/dashboard-front/src/views/resource/setting/edit.vue b/src/dashboard-front/src/views/resource/setting/edit.vue index 09c1bd9e5..cd09ec8eb 100644 --- a/src/dashboard-front/src/views/resource/setting/edit.vue +++ b/src/dashboard-front/src/views/resource/setting/edit.vue @@ -114,9 +114,11 @@ const getResourceDetails = async () => { // 提交 const handleSubmit = async () => { - await baseInfoRef.value?.validate(); - await frontConfigRef.value?.validate(); - await backConfigRef.value?.validate(); + await Promise.all([ + baseInfoRef.value?.validate(), + frontConfigRef.value?.validate(), + backConfigRef.value?.validate(), + ]); const baseFormData = baseInfoRef.value.formData; const frontFormData = frontConfigRef.value.frontConfigData; const backFormData = backConfigRef.value.backConfigData; diff --git a/src/dashboard-front/src/views/resource/setting/import.vue b/src/dashboard-front/src/views/resource/setting/import.vue index a4ad27bca..701289a36 100644 --- a/src/dashboard-front/src/views/resource/setting/import.vue +++ b/src/dashboard-front/src/views/resource/setting/import.vue @@ -1,6 +1,19 @@ + + + + + + + +
+
+
+ + {{ t('共') }}{{ tableData.length }}{{ t('个资源,新增') }}{{ tableDataToAdd.length }}{{ t('个,更新') }}{{ tableDataToUpdate.length }}{{ t('个,取消导入') }}{{ + tableDataUnchecked.length + }}{{ t('个') }} +
+ +
+ +
+ - - +
+ {{ t('新增的资源(共{num}个)', { num: tableDataToAdd.length }) }} + +
+ + +
+
+ +
+ - - +
+ {{ t('更新的资源(共{num}个)', { num: tableDataToUpdate.length }) }} + +
+ + +
+
+ +
+ - - diff --git a/src/dashboard-front/yarn.lock b/src/dashboard-front/yarn.lock index 2276105e3..dd891a412 100644 --- a/src/dashboard-front/yarn.lock +++ b/src/dashboard-front/yarn.lock @@ -1555,6 +1555,16 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsep-plugin/assignment@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jsep-plugin/assignment/-/assignment-1.2.1.tgz#07277bdd7862451a865d391e2142efba33f46c9b" + integrity sha512-gaHqbubTi29aZpVbBlECRpmdia+L5/lh2BwtIJTmtxdbecEyyX/ejAOg7eQDGNvGOUmPY7Z2Yxdy9ioyH/VJeA== + +"@jsep-plugin/regex@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@jsep-plugin/regex/-/regex-1.0.3.tgz#3aeaa2e5fa45d89de116aeafbfa41c95935b7f6d" + integrity sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" @@ -5361,7 +5371,7 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -5371,6 +5381,11 @@ jsbn@~0.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsep@^1.3.8: + version "1.3.9" + resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.3.9.tgz#8ce42df80ee9c1b39e52d0dd062a465342f35440" + integrity sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -5447,6 +5462,15 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonpath-plus@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-9.0.0.tgz#bb8703ee481531142bca8dee9a42fe72b8358a7f" + integrity sha512-bqE77VIDStrOTV/czspZhTn+o27Xx9ZJRGVkdVShEtPoqsIx5yALv3lWVU6y+PqYvWPJNWE7ORCQheQkEe0DDA== + dependencies: + "@jsep-plugin/assignment" "^1.2.1" + "@jsep-plugin/regex" "^1.0.3" + jsep "^1.3.8" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz"