mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 12:23:03 +00:00
js演练场
This commit is contained in:
309
web-front/PLAYGROUND_UI_UPGRADE.md
Normal file
309
web-front/PLAYGROUND_UI_UPGRADE.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# 演练场界面升级完成
|
||||
|
||||
## ✅ 已完成的功能
|
||||
|
||||
### 1. IDE风格工具栏
|
||||
|
||||
**新的工具栏布局**:
|
||||
- 运行按钮(带loading动画)+ 快捷键提示
|
||||
- 保存、格式化按钮组
|
||||
- 主题切换下拉菜单(3种主题)
|
||||
- 全屏按钮
|
||||
- 更多操作下拉菜单
|
||||
|
||||
**改进点**:
|
||||
- 更清晰的视觉层次
|
||||
- 图标 + 文字组合
|
||||
- 快捷键提示(tooltip)
|
||||
- 响应式布局适配
|
||||
|
||||
---
|
||||
|
||||
### 2. 全局快捷键系统
|
||||
|
||||
使用 `@vueuse/core` 的 `useMagicKeys` 实现:
|
||||
|
||||
| 快捷键 | 功能 | 实现方式 |
|
||||
|--------|------|---------|
|
||||
| `Ctrl/Cmd + Enter` | 运行测试 | executeTest() |
|
||||
| `Ctrl/Cmd + S` | 保存代码 | saveCode() |
|
||||
| `Shift + Alt + F` | 格式化代码 | formatCode() |
|
||||
| `F11` | 全屏模式 | toggleFullscreen() |
|
||||
| `Ctrl/Cmd + L` | 清空控制台 | clearConsoleLogs() |
|
||||
| `Ctrl/Cmd + R` | 重置代码 | loadTemplate() |
|
||||
| `Ctrl/Cmd + /` | 快捷键帮助 | showShortcutsHelp() |
|
||||
|
||||
**特点**:
|
||||
- 自动阻止浏览器默认行为(Ctrl+S保存、Ctrl+R刷新等)
|
||||
- Mac和Windows都支持
|
||||
- 实时响应,无延迟
|
||||
|
||||
---
|
||||
|
||||
### 3. 主题切换系统
|
||||
|
||||
**三种主题**:
|
||||
1. **Light** - 明亮主题(vs编辑器 + 浅色页面)
|
||||
2. **Dark** - 暗色主题(vs-dark编辑器 + 暗色页面)
|
||||
3. **High Contrast** - 高对比度(hc-black编辑器 + 暗色页面)
|
||||
|
||||
**同步切换**:
|
||||
- Monaco编辑器主题
|
||||
- Element Plus页面主题
|
||||
- 自动保存到localStorage
|
||||
|
||||
**切换方式**:
|
||||
- 点击工具栏主题下拉菜单
|
||||
- 图标随主题变化(Sunny/Moon/MostlyCloudy)
|
||||
|
||||
---
|
||||
|
||||
### 4. 可拖拽分栏布局
|
||||
|
||||
使用 `splitpanes` 库实现:
|
||||
|
||||
**布局结构**:
|
||||
```
|
||||
+------------------------------------------+
|
||||
| [代码编辑器] | [测试参数 + 结果] |
|
||||
| | |
|
||||
| 70% | 30% |
|
||||
| 可拖拽调整 ← → | |
|
||||
+------------------------------------------+
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 左右分栏可拖拽调整大小
|
||||
- 最小宽度限制(30% - 20%)
|
||||
- 平滑过渡动画
|
||||
- 响应式适配
|
||||
|
||||
---
|
||||
|
||||
### 5. 区域折叠功能
|
||||
|
||||
**可折叠的区域**:
|
||||
1. ✅ 右侧整体面板 - 折叠后编辑器占满全屏
|
||||
2. ✅ 测试参数卡片 - 独立折叠
|
||||
3. ✅ 测试结果卡片 - 独立折叠
|
||||
4. ✅ 控制台日志卡片 - 独立折叠
|
||||
5. ✅ 使用说明卡片 - 默认折叠
|
||||
|
||||
**折叠按钮**:
|
||||
- 卡片header右侧的箭头按钮
|
||||
- 右侧整体面板:左侧边缘的折叠按钮
|
||||
- 折叠后:固定的展开按钮
|
||||
|
||||
**状态持久化**:
|
||||
- 自动保存到localStorage
|
||||
- 页面刷新后保持折叠状态
|
||||
|
||||
---
|
||||
|
||||
### 6. 全屏模式
|
||||
|
||||
**实现方式**:
|
||||
- 使用 `@vueuse/core` 的 `useFullscreen`
|
||||
- 支持浏览器原生全屏API
|
||||
|
||||
**触发方式**:
|
||||
- F11快捷键
|
||||
- 工具栏全屏按钮
|
||||
- 图标随状态变化
|
||||
|
||||
**效果**:
|
||||
- 容器填充整个屏幕
|
||||
- 自动调整padding为0
|
||||
- z-index提升到最高层
|
||||
|
||||
---
|
||||
|
||||
### 7. 快捷键帮助弹窗
|
||||
|
||||
**内容**:
|
||||
- 表格形式展示所有快捷键
|
||||
- 功能名称 + 快捷键标签
|
||||
|
||||
**触发方式**:
|
||||
- Ctrl/Cmd + / 快捷键
|
||||
- 工具栏"更多"菜单中的"快捷键"选项
|
||||
|
||||
---
|
||||
|
||||
### 8. UI/UX改进
|
||||
|
||||
**视觉优化**:
|
||||
- 使用CSS变量适配明暗主题
|
||||
- 平滑的过渡动画(0.3s cubic-bezier)
|
||||
- 悬停效果优化
|
||||
- 按钮点击缩放反馈
|
||||
- 改进的滚动条样式
|
||||
|
||||
**交互优化**:
|
||||
- 控制台显示日志数量标签
|
||||
- JS日志特殊样式(绿色主题)
|
||||
- 卡片悬停阴影效果
|
||||
- 更好的视觉层次
|
||||
|
||||
**响应式设计**:
|
||||
- 移动端自动调整布局
|
||||
- 小屏幕优化
|
||||
- 触摸设备友好
|
||||
|
||||
---
|
||||
|
||||
## 🎨 新增的UI元素
|
||||
|
||||
### 工具栏
|
||||
- 运行按钮(CaretRight图标 + loading状态)
|
||||
- 按钮组(视觉分组)
|
||||
- 主题切换下拉菜单(带图标)
|
||||
- 全屏按钮
|
||||
- 更多操作菜单
|
||||
|
||||
### 折叠按钮
|
||||
- 右侧面板折叠按钮(蓝色浮动按钮)
|
||||
- 卡片折叠箭头(ArrowUp/ArrowDown)
|
||||
- 展开按钮(固定在右侧边缘)
|
||||
|
||||
### 状态指示
|
||||
- 控制台日志数量标签
|
||||
- 主题名称显示
|
||||
- 加载状态动画
|
||||
|
||||
---
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 依赖库
|
||||
- `@vueuse/core` - 快捷键、全屏API
|
||||
- `splitpanes` - 可拖拽分栏
|
||||
- `element-plus` - UI组件库
|
||||
- `vue3-json-viewer` - JSON查看器
|
||||
|
||||
### 核心代码
|
||||
|
||||
**快捷键系统**:
|
||||
```javascript
|
||||
import { useMagicKeys, useFullscreen, useEventListener } from '@vueuse/core';
|
||||
|
||||
const keys = useMagicKeys();
|
||||
const ctrlEnter = keys['Ctrl+Enter'];
|
||||
|
||||
watch(ctrlEnter, (pressed) => {
|
||||
if (pressed) executeTest();
|
||||
});
|
||||
```
|
||||
|
||||
**折叠功能**:
|
||||
```javascript
|
||||
const collapsedPanels = ref({
|
||||
rightPanel: false,
|
||||
testParams: false,
|
||||
testResult: false,
|
||||
console: false,
|
||||
help: true
|
||||
});
|
||||
|
||||
const togglePanel = (panelName) => {
|
||||
collapsedPanels.value[panelName] = !collapsedPanels.value[panelName];
|
||||
localStorage.setItem('playground_collapsed_panels', JSON.stringify(collapsedPanels.value));
|
||||
};
|
||||
```
|
||||
|
||||
**主题切换**:
|
||||
```javascript
|
||||
const changeTheme = (themeName) => {
|
||||
const theme = themes.find(t => t.name === themeName);
|
||||
if (theme.page === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
localStorage.setItem('playground_theme', themeName);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 改进对比
|
||||
|
||||
| 特性 | 改进前 | 改进后 |
|
||||
|------|--------|--------|
|
||||
| 工具栏 | 简单按钮排列 | IDE风格分组工具栏 |
|
||||
| 布局 | 固定16:8比例 | 可拖拽调整Splitpanes |
|
||||
| 折叠 | 仅使用说明可折叠 | 所有区域可独立折叠 |
|
||||
| 快捷键 | 无 | 7个常用快捷键 |
|
||||
| 主题 | 跟随系统 | 3种主题自由切换 |
|
||||
| 全屏 | 无 | 支持F11全屏模式 |
|
||||
| 响应式 | 基础 | 完整的移动端适配 |
|
||||
| 动画 | 无 | 平滑的折叠/展开动画 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 如何使用新功能
|
||||
|
||||
### 主题切换
|
||||
1. 点击工具栏的主题按钮
|
||||
2. 选择Light/Dark/High Contrast
|
||||
3. 编辑器和页面同步切换
|
||||
|
||||
### 折叠面板
|
||||
1. 点击卡片header的箭头按钮折叠该卡片
|
||||
2. 点击右侧边缘的按钮折叠整个右侧面板
|
||||
3. 折叠后点击浮动按钮展开
|
||||
|
||||
### 调整布局
|
||||
1. 拖拽中间的分隔线调整左右比例
|
||||
2. 右侧面板折叠后编辑器自动占满
|
||||
|
||||
### 使用快捷键
|
||||
1. 按 `Ctrl+/` 查看所有快捷键
|
||||
2. 使用快捷键快速操作
|
||||
3. 工具提示会显示对应的快捷键
|
||||
|
||||
---
|
||||
|
||||
## 🎯 下一步
|
||||
|
||||
1. **重新编译前端**:
|
||||
```bash
|
||||
cd web-front
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. **复制到部署目录**:
|
||||
```bash
|
||||
cp -r nfd-front/* ../webroot/nfd-front/
|
||||
```
|
||||
|
||||
3. **测试功能**:
|
||||
- 打开演练场页面
|
||||
- 测试所有快捷键
|
||||
- 测试主题切换
|
||||
- 测试折叠功能
|
||||
- 测试全屏模式
|
||||
- 测试拖拽调整布局
|
||||
|
||||
---
|
||||
|
||||
## 🐛 已知问题
|
||||
|
||||
无
|
||||
|
||||
---
|
||||
|
||||
## 💡 使用提示
|
||||
|
||||
1. **首次使用**: 点击"快捷键"按钮查看所有可用快捷键
|
||||
2. **调整布局**: 拖拽分隔线找到最适合你的布局
|
||||
3. **专注编码**: 折叠右侧面板获得更大编辑空间
|
||||
4. **保护眼睛**: 使用暗色主题减少疲劳
|
||||
5. **快速测试**: Ctrl+Enter直接运行,无需鼠标
|
||||
|
||||
---
|
||||
|
||||
**升级日期**: 2025-11-29
|
||||
**版本**: v2.0
|
||||
**状态**: ✅ 完成
|
||||
|
||||
196
web-front/src/components/MonacoEditor.vue
Normal file
196
web-front/src/components/MonacoEditor.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div ref="editorContainer" class="monaco-editor-container"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'MonacoEditor',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: 'javascript'
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'vs'
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '500px'
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const editorContainer = ref(null);
|
||||
let editor = null;
|
||||
let monaco = null;
|
||||
|
||||
const defaultOptions = {
|
||||
value: props.modelValue,
|
||||
language: props.language,
|
||||
theme: props.theme,
|
||||
automaticLayout: true,
|
||||
fontSize: 14,
|
||||
minimap: {
|
||||
enabled: true
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
roundedSelection: false,
|
||||
readOnly: false,
|
||||
cursorStyle: 'line',
|
||||
formatOnPaste: true,
|
||||
formatOnType: true,
|
||||
tabSize: 2,
|
||||
insertSpaces: true,
|
||||
...props.options
|
||||
};
|
||||
|
||||
const initEditor = async () => {
|
||||
try {
|
||||
if (!editorContainer.value) {
|
||||
console.error('编辑器容器未找到');
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态导入monaco-editor loader
|
||||
let loaderModule;
|
||||
try {
|
||||
loaderModule = await import('@monaco-editor/loader');
|
||||
} catch (importError) {
|
||||
console.error('导入@monaco-editor/loader失败:', importError);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取loader对象
|
||||
// @monaco-editor/loader可能使用default导出或named导出
|
||||
let loader;
|
||||
if (loaderModule.default) {
|
||||
loader = loaderModule.default;
|
||||
} else if (loaderModule.loader) {
|
||||
loader = loaderModule.loader;
|
||||
} else {
|
||||
loader = loaderModule;
|
||||
}
|
||||
|
||||
if (!loader) {
|
||||
console.error('Monaco Editor loader未找到,loaderModule:', loaderModule);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof loader.init !== 'function') {
|
||||
console.error('loader.init不是函数,loader对象:', loader);
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化Monaco Editor
|
||||
monaco = await loader.init();
|
||||
|
||||
if (!monaco) {
|
||||
console.error('loader.init返回null或undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!monaco.editor) {
|
||||
console.error('monaco.editor不存在,monaco对象:', monaco);
|
||||
return;
|
||||
}
|
||||
|
||||
editor = monaco.editor.create(editorContainer.value, {
|
||||
...defaultOptions,
|
||||
value: props.modelValue
|
||||
});
|
||||
|
||||
// 监听内容变化
|
||||
editor.onDidChangeModelContent(() => {
|
||||
const value = editor.getValue();
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
});
|
||||
|
||||
// 设置容器高度
|
||||
if (editorContainer.value) {
|
||||
editorContainer.value.style.height = props.height;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Monaco Editor初始化失败:', error);
|
||||
console.error('错误详情:', error.stack);
|
||||
console.error('错误对象:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const updateTheme = (newTheme) => {
|
||||
if (editor) {
|
||||
monaco.editor.setTheme(newTheme);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDocument = () => {
|
||||
if (editor) {
|
||||
editor.getAction('editor.action.formatDocument').run();
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (editor && editor.getValue() !== newValue) {
|
||||
editor.setValue(newValue);
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.theme, (newTheme) => {
|
||||
updateTheme(newTheme);
|
||||
});
|
||||
|
||||
watch(() => props.height, (newHeight) => {
|
||||
if (editorContainer.value) {
|
||||
editorContainer.value.style.height = newHeight;
|
||||
if (editor) {
|
||||
editor.layout();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
initEditor();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
editorContainer,
|
||||
formatDocument,
|
||||
getEditor: () => editor,
|
||||
getMonaco: () => monaco
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monaco-editor-container {
|
||||
width: 100%;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-editor-container :deep(.monaco-editor) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
359
web-front/src/utils/monacoTypes.js
Normal file
359
web-front/src/utils/monacoTypes.js
Normal file
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* Monaco Editor 代码补全配置工具
|
||||
* 基于 types.js 提供完整的代码补全支持
|
||||
*/
|
||||
|
||||
/**
|
||||
* 配置Monaco Editor的类型定义和代码补全
|
||||
* @param {monaco} monaco - Monaco Editor实例
|
||||
*/
|
||||
export async function configureMonacoTypes(monaco) {
|
||||
if (!monaco) {
|
||||
console.warn('Monaco Editor未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册JavaScript语言特性
|
||||
monaco.languages.setLanguageConfiguration('javascript', {
|
||||
comments: {
|
||||
lineComment: '//',
|
||||
blockComment: ['/*', '*/']
|
||||
},
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')']
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" }
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" }
|
||||
]
|
||||
});
|
||||
|
||||
// 注册类型定义
|
||||
registerTypeDefinitions(monaco);
|
||||
|
||||
// 注册代码补全提供者
|
||||
registerCompletionProvider(monaco);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册类型定义
|
||||
*/
|
||||
function registerTypeDefinitions(monaco) {
|
||||
// ShareLinkInfo类型定义
|
||||
const shareLinkInfoType = `
|
||||
interface ShareLinkInfo {
|
||||
getShareUrl(): string;
|
||||
getShareKey(): string;
|
||||
getSharePassword(): string;
|
||||
getType(): string;
|
||||
getPanName(): string;
|
||||
getOtherParam(key: string): any;
|
||||
hasOtherParam(key: string): boolean;
|
||||
getOtherParamAsString(key: string): string | null;
|
||||
getOtherParamAsInteger(key: string): number | null;
|
||||
getOtherParamAsBoolean(key: string): boolean | null;
|
||||
}
|
||||
`;
|
||||
|
||||
// JsHttpClient类型定义
|
||||
const httpClientType = `
|
||||
interface JsHttpClient {
|
||||
get(url: string): JsHttpResponse;
|
||||
getWithRedirect(url: string): JsHttpResponse;
|
||||
getNoRedirect(url: string): JsHttpResponse;
|
||||
post(url: string, data?: any): JsHttpResponse;
|
||||
put(url: string, data?: any): JsHttpResponse;
|
||||
delete(url: string): JsHttpResponse;
|
||||
patch(url: string, data?: any): JsHttpResponse;
|
||||
putHeader(name: string, value: string): JsHttpClient;
|
||||
putHeaders(headers: Record<string, string>): JsHttpClient;
|
||||
removeHeader(name: string): JsHttpClient;
|
||||
clearHeaders(): JsHttpClient;
|
||||
getHeaders(): Record<string, string>;
|
||||
setTimeout(seconds: number): JsHttpClient;
|
||||
sendForm(data: Record<string, any>): JsHttpResponse;
|
||||
sendMultipartForm(url: string, data: Record<string, any>): JsHttpResponse;
|
||||
sendJson(data: any): JsHttpResponse;
|
||||
urlEncode(str: string): string;
|
||||
urlDecode(str: string): string;
|
||||
}
|
||||
`;
|
||||
|
||||
// JsHttpResponse类型定义
|
||||
const httpResponseType = `
|
||||
interface JsHttpResponse {
|
||||
body(): string;
|
||||
json(): any;
|
||||
statusCode(): number;
|
||||
header(name: string): string | null;
|
||||
headers(): Record<string, string>;
|
||||
isSuccess(): boolean;
|
||||
bodyBytes(): number[];
|
||||
bodySize(): number;
|
||||
}
|
||||
`;
|
||||
|
||||
// JsLogger类型定义
|
||||
const loggerType = `
|
||||
interface JsLogger {
|
||||
debug(message: string, ...args: any[]): void;
|
||||
info(message: string, ...args: any[]): void;
|
||||
warn(message: string, ...args: any[]): void;
|
||||
error(message: string, ...args: any[]): void;
|
||||
isDebugEnabled(): boolean;
|
||||
isInfoEnabled(): boolean;
|
||||
isWarnEnabled(): boolean;
|
||||
isErrorEnabled(): boolean;
|
||||
}
|
||||
`;
|
||||
|
||||
// FileInfo类型定义
|
||||
const fileInfoType = `
|
||||
interface FileInfo {
|
||||
fileName: string;
|
||||
fileId: string;
|
||||
fileType: 'file' | 'folder';
|
||||
size: number;
|
||||
sizeStr: string;
|
||||
createTime: string;
|
||||
updateTime?: string;
|
||||
createBy?: string;
|
||||
downloadCount?: number;
|
||||
fileIcon?: string;
|
||||
panType?: string;
|
||||
parserUrl?: string;
|
||||
previewUrl?: string;
|
||||
}
|
||||
`;
|
||||
|
||||
// 合并所有类型定义
|
||||
const allTypes = `
|
||||
${shareLinkInfoType}
|
||||
${httpClientType}
|
||||
${httpResponseType}
|
||||
${loggerType}
|
||||
${fileInfoType}
|
||||
|
||||
// 全局变量声明
|
||||
declare var shareLinkInfo: ShareLinkInfo;
|
||||
declare var http: JsHttpClient;
|
||||
declare var logger: JsLogger;
|
||||
`;
|
||||
|
||||
// 注册类型定义到Monaco
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
allTypes,
|
||||
'file:///types.d.ts'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册代码补全提供者
|
||||
*/
|
||||
function registerCompletionProvider(monaco) {
|
||||
monaco.languages.registerCompletionItemProvider('javascript', {
|
||||
provideCompletionItems: (model, position) => {
|
||||
const word = model.getWordUntilPosition(position);
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endColumn: word.endColumn
|
||||
};
|
||||
|
||||
const suggestions = [
|
||||
// ShareLinkInfo方法
|
||||
{
|
||||
label: 'shareLinkInfo.getShareUrl()',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'shareLinkInfo.getShareUrl()',
|
||||
documentation: '获取分享URL',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'shareLinkInfo.getShareKey()',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'shareLinkInfo.getShareKey()',
|
||||
documentation: '获取分享Key',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'shareLinkInfo.getSharePassword()',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'shareLinkInfo.getSharePassword()',
|
||||
documentation: '获取分享密码',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'shareLinkInfo.getType()',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'shareLinkInfo.getType()',
|
||||
documentation: '获取网盘类型',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'shareLinkInfo.getPanName()',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'shareLinkInfo.getPanName()',
|
||||
documentation: '获取网盘名称',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'shareLinkInfo.getOtherParam(key)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'shareLinkInfo.getOtherParam(${1:key})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '获取其他参数',
|
||||
range
|
||||
},
|
||||
// JsHttpClient方法
|
||||
{
|
||||
label: 'http.get(url)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'http.get(${1:url})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '发起GET请求',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'http.post(url, data)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'http.post(${1:url}, ${2:data})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '发起POST请求',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'http.putHeader(name, value)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'http.putHeader(${1:name}, ${2:value})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '设置请求头',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'http.sendForm(data)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'http.sendForm(${1:data})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '发送表单数据',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'http.sendJson(data)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'http.sendJson(${1:data})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '发送JSON数据',
|
||||
range
|
||||
},
|
||||
// JsLogger方法
|
||||
{
|
||||
label: 'logger.info(message)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'logger.info(${1:message})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '记录信息日志',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'logger.debug(message)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'logger.debug(${1:message})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '记录调试日志',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'logger.warn(message)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'logger.warn(${1:message})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '记录警告日志',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'logger.error(message)',
|
||||
kind: monaco.languages.CompletionItemKind.Method,
|
||||
insertText: 'logger.error(${1:message})',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
documentation: '记录错误日志',
|
||||
range
|
||||
}
|
||||
];
|
||||
|
||||
return { suggestions };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从API获取types.js内容并配置
|
||||
*/
|
||||
export async function loadTypesFromApi(monaco) {
|
||||
try {
|
||||
// 先尝试从缓存加载
|
||||
const cacheKey = 'playground_types_js';
|
||||
const cachedContent = localStorage.getItem(cacheKey);
|
||||
if (cachedContent) {
|
||||
try {
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
cachedContent,
|
||||
'file:///types.js'
|
||||
);
|
||||
console.log('从缓存加载types.js成功');
|
||||
// 异步更新缓存
|
||||
updateTypesJsCache();
|
||||
return;
|
||||
} catch (error) {
|
||||
console.warn('使用缓存的types.js失败,重新加载:', error);
|
||||
localStorage.removeItem(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
// 从API加载
|
||||
const response = await fetch('/v2/playground/types.js');
|
||||
if (response.ok) {
|
||||
const typesJsContent = await response.text();
|
||||
// 缓存到localStorage
|
||||
localStorage.setItem(cacheKey, typesJsContent);
|
||||
// 添加到类型定义中
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
typesJsContent,
|
||||
'file:///types.js'
|
||||
);
|
||||
console.log('加载types.js成功并已缓存');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('加载types.js失败,使用内置类型定义:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步更新types.js缓存
|
||||
*/
|
||||
async function updateTypesJsCache() {
|
||||
try {
|
||||
const response = await fetch('/v2/playground/types.js');
|
||||
if (response.ok) {
|
||||
const typesJsContent = await response.text();
|
||||
localStorage.setItem('playground_types_js', typesJsContent);
|
||||
console.log('types.js缓存已更新');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('更新types.js缓存失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
146
web-front/src/utils/playgroundApi.js
Normal file
146
web-front/src/utils/playgroundApi.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* 演练场API服务
|
||||
*/
|
||||
export const playgroundApi = {
|
||||
/**
|
||||
* 测试执行JavaScript代码
|
||||
* @param {string} jsCode - JavaScript代码
|
||||
* @param {string} shareUrl - 分享链接
|
||||
* @param {string} pwd - 密码(可选)
|
||||
* @param {string} method - 测试方法:parse/parseFileList/parseById
|
||||
* @returns {Promise} 测试结果
|
||||
*/
|
||||
async testScript(jsCode, shareUrl, pwd = '', method = 'parse') {
|
||||
try {
|
||||
const response = await axios.post('/v2/playground/test', {
|
||||
jsCode,
|
||||
shareUrl,
|
||||
pwd,
|
||||
method
|
||||
});
|
||||
// 框架会自动包装成JsonResult,需要从data字段获取
|
||||
if (response.data && response.data.data) {
|
||||
return response.data.data;
|
||||
}
|
||||
// 如果没有包装,直接返回
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
const errorMsg = error.response?.data?.data?.error ||
|
||||
error.response?.data?.error ||
|
||||
error.response?.data?.msg ||
|
||||
error.message ||
|
||||
'测试执行失败';
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取types.js文件内容
|
||||
* @returns {Promise<string>} types.js内容
|
||||
*/
|
||||
async getTypesJs() {
|
||||
try {
|
||||
const response = await axios.get('/v2/playground/types.js', {
|
||||
responseType: 'text'
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '获取types.js失败');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取解析器列表
|
||||
*/
|
||||
async getParserList() {
|
||||
try {
|
||||
const response = await axios.get('/v2/playground/parsers');
|
||||
// 框架会自动包装成JsonResult,需要从data字段获取
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
code: response.data.code || 200,
|
||||
data: response.data.data,
|
||||
msg: response.data.msg,
|
||||
success: response.data.success
|
||||
};
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取解析器列表失败');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存解析器
|
||||
*/
|
||||
async saveParser(jsCode) {
|
||||
try {
|
||||
const response = await axios.post('/v2/playground/parsers', { jsCode });
|
||||
// 框架会自动包装成JsonResult
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
code: response.data.code || 200,
|
||||
data: response.data.data,
|
||||
msg: response.data.msg,
|
||||
success: response.data.success
|
||||
};
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
const errorMsg = error.response?.data?.data?.error ||
|
||||
error.response?.data?.error ||
|
||||
error.response?.data?.msg ||
|
||||
error.message ||
|
||||
'保存解析器失败';
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新解析器
|
||||
*/
|
||||
async updateParser(id, jsCode, enabled = true) {
|
||||
try {
|
||||
const response = await axios.put(`/v2/playground/parsers/${id}`, { jsCode, enabled });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '更新解析器失败');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除解析器
|
||||
*/
|
||||
async deleteParser(id) {
|
||||
try {
|
||||
const response = await axios.delete(`/v2/playground/parsers/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '删除解析器失败');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 根据ID获取解析器
|
||||
*/
|
||||
async getParserById(id) {
|
||||
try {
|
||||
const response = await axios.get(`/v2/playground/parsers/${id}`);
|
||||
// 框架会自动包装成JsonResult
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
code: response.data.code || 200,
|
||||
data: response.data.data,
|
||||
msg: response.data.msg,
|
||||
success: response.data.success
|
||||
};
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.response?.data?.msg || error.message || '获取解析器失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user