smart-code-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/lib/adapters/vanilla/index.d.ts +3 -0
- package/lib/adapters/vanilla/index.d.ts.map +1 -0
- package/lib/config/languages.d.ts +21 -0
- package/lib/config/languages.d.ts.map +1 -0
- package/lib/config/runnerStrategies.d.ts +12 -0
- package/lib/config/runnerStrategies.d.ts.map +1 -0
- package/lib/config/runnerStrategies_v2.d.ts +31 -0
- package/lib/config/runnerStrategies_v2.d.ts.map +1 -0
- package/lib/config/themes.d.ts +54 -0
- package/lib/config/themes.d.ts.map +1 -0
- package/lib/core/BackendRunner.d.ts +78 -0
- package/lib/core/BackendRunner.d.ts.map +1 -0
- package/lib/core/CodeRunner.d.ts +32 -0
- package/lib/core/CodeRunner.d.ts.map +1 -0
- package/lib/core/LanguageManager.d.ts +41 -0
- package/lib/core/LanguageManager.d.ts.map +1 -0
- package/lib/core/LayoutManager.d.ts +59 -0
- package/lib/core/LayoutManager.d.ts.map +1 -0
- package/lib/core/MonacoWrapper.d.ts +63 -0
- package/lib/core/MonacoWrapper.d.ts.map +1 -0
- package/lib/core/SmartCodeEditor.d.ts +140 -0
- package/lib/core/SmartCodeEditor.d.ts.map +1 -0
- package/lib/dev-main.d.ts +2 -0
- package/lib/dev-main.d.ts.map +1 -0
- package/lib/index.cjs +242 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1369 -0
- package/lib/index.umd.cjs +242 -0
- package/lib/shims-vue.d.ts +4 -0
- package/lib/types/index.d.ts +101 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/language.d.ts +37 -0
- package/lib/types/language.d.ts.map +1 -0
- package/lib/types/question.d.ts +75 -0
- package/lib/types/question.d.ts.map +1 -0
- package/lib/utils/loader.d.ts +9 -0
- package/lib/utils/loader.d.ts.map +1 -0
- package/lib/utils/markdown.d.ts +2 -0
- package/lib/utils/markdown.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/adapters/vanilla/index.ts +7 -0
- package/src/adapters/vue/SmartCodeEditor.vue +1190 -0
- package/src/config/languages.ts +273 -0
- package/src/config/runnerStrategies.ts +261 -0
- package/src/config/runnerStrategies_v2.ts +182 -0
- package/src/config/themes.ts +37 -0
- package/src/core/BackendRunner.ts +329 -0
- package/src/core/CodeRunner.ts +107 -0
- package/src/core/LanguageManager.ts +108 -0
- package/src/core/LayoutManager.ts +268 -0
- package/src/core/MonacoWrapper.ts +173 -0
- package/src/core/SmartCodeEditor.ts +1015 -0
- package/src/dev-app.vue +488 -0
- package/src/dev-main.ts +7 -0
- package/src/index.ts +19 -0
- package/src/shims-vue.d.ts +4 -0
- package/src/types/index.ts +129 -0
- package/src/types/language.ts +44 -0
- package/src/types/question.ts +98 -0
- package/src/utils/loader.ts +69 -0
- package/src/utils/markdown.ts +89 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { LanguageMetadata } from "../types/language";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*语言元数据配置列表
|
|
5
|
+
* 只包含语言的静态元数据,不包含题目相关的代码模板
|
|
6
|
+
* ⭐ 新增语言只需在这里添加基础配置
|
|
7
|
+
*/
|
|
8
|
+
export const LANGUAGE_CONFIGS: LanguageMetadata[] = [
|
|
9
|
+
{
|
|
10
|
+
id: "javascript",
|
|
11
|
+
name: "JavaScript",
|
|
12
|
+
monacoId: "javascript",
|
|
13
|
+
extensions: [".js"],
|
|
14
|
+
fallbackTemplate: `// JavaScript 示例
|
|
15
|
+
console.log("Hello, World!");
|
|
16
|
+
|
|
17
|
+
// 定义一个函数
|
|
18
|
+
function fibonacci(n) {
|
|
19
|
+
if (n <= 1) return n;
|
|
20
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("斐波那契数列前10项:");
|
|
24
|
+
for (let i = 0; i < 10; i++) {
|
|
25
|
+
console.log(\`F(\${i}) = \${fibonacci(i)}\`);
|
|
26
|
+
}`,
|
|
27
|
+
canRun: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "typescript",
|
|
31
|
+
name: "TypeScript",
|
|
32
|
+
monacoId: "typescript",
|
|
33
|
+
extensions: [".ts"],
|
|
34
|
+
fallbackTemplate: `// TypeScript 示例
|
|
35
|
+
interface User {
|
|
36
|
+
name: string;
|
|
37
|
+
age: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const user: User = {
|
|
41
|
+
name: "张三",
|
|
42
|
+
age: 25
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
console.log(\`用户: \${user.name}, 年龄: \${user.age}\`);`,
|
|
46
|
+
canRun: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "python",
|
|
50
|
+
name: "Python",
|
|
51
|
+
monacoId: "python",
|
|
52
|
+
extensions: [".py"],
|
|
53
|
+
fallbackTemplate: `# Python 示例
|
|
54
|
+
print("Hello, World!")
|
|
55
|
+
|
|
56
|
+
# 列表推导式
|
|
57
|
+
squares = [x**2 for x in range(10)]
|
|
58
|
+
print("前10个平方数:", squares)
|
|
59
|
+
|
|
60
|
+
# 定义一个类
|
|
61
|
+
class Person:
|
|
62
|
+
def __init__(self, name, age):
|
|
63
|
+
self.name = name
|
|
64
|
+
self.age = age
|
|
65
|
+
|
|
66
|
+
def greet(self):
|
|
67
|
+
return f"你好,我是{self.name},今年{self.age}岁"
|
|
68
|
+
|
|
69
|
+
person = Person("李四", 30)
|
|
70
|
+
print(person.greet())`,
|
|
71
|
+
canRun: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "html",
|
|
75
|
+
name: "HTML",
|
|
76
|
+
monacoId: "html",
|
|
77
|
+
fallbackTemplate: `<!DOCTYPE html>
|
|
78
|
+
<html lang="zh-CN">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="UTF-8">
|
|
81
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
82
|
+
<title>Hello HTML</title>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<h1>欢迎使用智能代码编辑器</h1>
|
|
86
|
+
<p>这是一个 HTML 示例</p>
|
|
87
|
+
</body>
|
|
88
|
+
</html>`,
|
|
89
|
+
canRun: false,
|
|
90
|
+
|
|
91
|
+
extensions: [".html"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "css",
|
|
95
|
+
name: "CSS",
|
|
96
|
+
monacoId: "css",
|
|
97
|
+
fallbackTemplate: `/* CSS 示例 */
|
|
98
|
+
body {
|
|
99
|
+
font-family: 'Arial', sans-serif;
|
|
100
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
101
|
+
color: white;
|
|
102
|
+
padding: 20px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.container {
|
|
106
|
+
max-width: 1200px;
|
|
107
|
+
margin: 0 auto;
|
|
108
|
+
background: rgba(255, 255, 255, 0.1);
|
|
109
|
+
border-radius: 10px;
|
|
110
|
+
padding: 30px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
h1 {
|
|
114
|
+
font-size: 2.5rem;
|
|
115
|
+
margin-bottom: 20px;
|
|
116
|
+
}`,
|
|
117
|
+
canRun: false,
|
|
118
|
+
|
|
119
|
+
extensions: [".css"],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "java",
|
|
123
|
+
name: "Java",
|
|
124
|
+
monacoId: "java",
|
|
125
|
+
fallbackTemplate: `public class HelloWorld {
|
|
126
|
+
public static void main(String[] args) {
|
|
127
|
+
System.out.println("Hello, World!");
|
|
128
|
+
|
|
129
|
+
// 数组示例
|
|
130
|
+
int[] numbers = {1, 2, 3, 4, 5};
|
|
131
|
+
int sum = 0;
|
|
132
|
+
for (int num : numbers) {
|
|
133
|
+
sum += num;
|
|
134
|
+
}
|
|
135
|
+
System.out.println("数组总和: " + sum);
|
|
136
|
+
}
|
|
137
|
+
}`,
|
|
138
|
+
canRun: true,
|
|
139
|
+
// 需要远程编译服务
|
|
140
|
+
extensions: [".java"],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "cpp",
|
|
144
|
+
name: "C++",
|
|
145
|
+
monacoId: "cpp",
|
|
146
|
+
fallbackTemplate: `#include <iostream>
|
|
147
|
+
#include <vector>
|
|
148
|
+
using namespace std;
|
|
149
|
+
|
|
150
|
+
int main() {
|
|
151
|
+
cout << "Hello, World!" << endl;
|
|
152
|
+
|
|
153
|
+
// 向量示例
|
|
154
|
+
vector<int> vec = {1, 2, 3, 4, 5};
|
|
155
|
+
int sum = 0;
|
|
156
|
+
for(int num : vec) {
|
|
157
|
+
sum += num;
|
|
158
|
+
}
|
|
159
|
+
cout << "向量总和: " << sum << endl;
|
|
160
|
+
|
|
161
|
+
return 0;
|
|
162
|
+
}`,
|
|
163
|
+
canRun: true,
|
|
164
|
+
|
|
165
|
+
extensions: [".cpp", ".cc", ".cxx"],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "go",
|
|
169
|
+
name: "Go",
|
|
170
|
+
monacoId: "go",
|
|
171
|
+
fallbackTemplate: `package main
|
|
172
|
+
|
|
173
|
+
import "fmt"
|
|
174
|
+
|
|
175
|
+
func main() {
|
|
176
|
+
fmt.Println("Hello, World!")
|
|
177
|
+
|
|
178
|
+
// 切片示例
|
|
179
|
+
numbers := []int{1, 2, 3, 4, 5}
|
|
180
|
+
sum := 0
|
|
181
|
+
for _, num := range numbers {
|
|
182
|
+
sum += num
|
|
183
|
+
}
|
|
184
|
+
fmt.Printf("切片总和: %d\\n", sum)
|
|
185
|
+
}`,
|
|
186
|
+
canRun: true,
|
|
187
|
+
|
|
188
|
+
extensions: [".go"],
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "rust",
|
|
192
|
+
name: "Rust",
|
|
193
|
+
monacoId: "rust",
|
|
194
|
+
fallbackTemplate: `fn main() {
|
|
195
|
+
println!("Hello, World!");
|
|
196
|
+
|
|
197
|
+
// 向量示例
|
|
198
|
+
let numbers = vec![1, 2, 3, 4, 5];
|
|
199
|
+
let sum: i32 = numbers.iter().sum();
|
|
200
|
+
println!("总和: {}", sum);
|
|
201
|
+
}`,
|
|
202
|
+
canRun: true,
|
|
203
|
+
|
|
204
|
+
extensions: [".rs"],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "c",
|
|
208
|
+
name: "C",
|
|
209
|
+
monacoId: "c",
|
|
210
|
+
fallbackTemplate: `#include <stdio.h>
|
|
211
|
+
|
|
212
|
+
int main() {
|
|
213
|
+
printf("Hello, World!\\n");
|
|
214
|
+
|
|
215
|
+
// 数组示例
|
|
216
|
+
int numbers[] = {1, 2, 3, 4, 5};
|
|
217
|
+
int sum = 0;
|
|
218
|
+
int size = sizeof(numbers) / sizeof(numbers[0]);
|
|
219
|
+
|
|
220
|
+
for(int i = 0; i < size; i++) {
|
|
221
|
+
sum += numbers[i];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
printf("数组总和: %d\\n", sum);
|
|
225
|
+
return 0;
|
|
226
|
+
}`,
|
|
227
|
+
canRun: true,
|
|
228
|
+
|
|
229
|
+
extensions: [".c"],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "sql",
|
|
233
|
+
name: "SQL",
|
|
234
|
+
monacoId: "sql",
|
|
235
|
+
fallbackTemplate: `-- SQL 示例
|
|
236
|
+
SELECT
|
|
237
|
+
users.name,
|
|
238
|
+
users.email,
|
|
239
|
+
COUNT(orders.id) as order_count,
|
|
240
|
+
SUM(orders.amount) as total_amount
|
|
241
|
+
FROM users
|
|
242
|
+
LEFT JOIN orders ON users.id = orders.user_id
|
|
243
|
+
WHERE users.created_at >= '2024-01-01'
|
|
244
|
+
GROUP BY users.id, users.name, users.email
|
|
245
|
+
HAVING COUNT(orders.id) > 0
|
|
246
|
+
ORDER BY total_amount DESC
|
|
247
|
+
LIMIT 10;`,
|
|
248
|
+
canRun: false,
|
|
249
|
+
|
|
250
|
+
extensions: [".sql"],
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 根据 ID 获取语言配置
|
|
256
|
+
*/
|
|
257
|
+
export function getLanguageConfig(id: string): LanguageMetadata | undefined {
|
|
258
|
+
return LANGUAGE_CONFIGS.find((lang) => lang.id === id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 获取所有支持的语言
|
|
263
|
+
*/
|
|
264
|
+
export function getAllLanguages(): LanguageMetadata[] {
|
|
265
|
+
return LANGUAGE_CONFIGS;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 获取可运行的语言列表
|
|
270
|
+
*/
|
|
271
|
+
export function getRunnableLanguages(): LanguageMetadata[] {
|
|
272
|
+
return LANGUAGE_CONFIGS.filter((lang) => lang.canRun);
|
|
273
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { RunResult, RunnerStrategy } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JavaScript/TypeScript 运行策略
|
|
5
|
+
*/
|
|
6
|
+
async function runJavaScript(
|
|
7
|
+
code: string,
|
|
8
|
+
testCases?: import("../types").TestCase[],
|
|
9
|
+
): Promise<RunResult> {
|
|
10
|
+
const startTime = performance.now();
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// 1. 如果没有测试用例,保持原有逻辑 (Console 模式)
|
|
14
|
+
if (!testCases || testCases.length === 0) {
|
|
15
|
+
// 创建一个安全的执行环境
|
|
16
|
+
const logs: string[] = [];
|
|
17
|
+
|
|
18
|
+
// 重写 console.log
|
|
19
|
+
const customConsole = {
|
|
20
|
+
log: (...args: any[]) => {
|
|
21
|
+
logs.push(
|
|
22
|
+
args
|
|
23
|
+
.map((arg) =>
|
|
24
|
+
typeof arg === "object"
|
|
25
|
+
? JSON.stringify(arg, null, 2)
|
|
26
|
+
: String(arg),
|
|
27
|
+
)
|
|
28
|
+
.join(" "),
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
error: (...args: any[]) => {
|
|
32
|
+
logs.push("ERROR: " + args.join(" "));
|
|
33
|
+
},
|
|
34
|
+
warn: (...args: any[]) => {
|
|
35
|
+
logs.push("WARN: " + args.join(" "));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// 使用 Function 构造器创建沙箱
|
|
40
|
+
const fn = new Function("console", code);
|
|
41
|
+
fn(customConsole);
|
|
42
|
+
|
|
43
|
+
const executionTime = performance.now() - startTime;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
output: logs.join("\n") || "(无输出)",
|
|
47
|
+
executionTime,
|
|
48
|
+
status: "success",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. 如果有测试用例 (Test Mode)
|
|
53
|
+
const logs: string[] = [];
|
|
54
|
+
const testResults: import("../types").TestCaseResult[] = [];
|
|
55
|
+
|
|
56
|
+
// 构造 wrapper 代码来获取用户函数
|
|
57
|
+
const wrapperCode = `
|
|
58
|
+
${code}
|
|
59
|
+
|
|
60
|
+
// 尝试找到入口函数
|
|
61
|
+
let mainFn = null;
|
|
62
|
+
// 优先级 1: solution 函数
|
|
63
|
+
if (typeof solution === 'function') mainFn = solution;
|
|
64
|
+
// 优先级 2: twoSum (针对示例)
|
|
65
|
+
else if (typeof twoSum === 'function') mainFn = twoSum;
|
|
66
|
+
// 优先级 3: 寻找最后一个定义的函数 (简单启发式,可能不准确)
|
|
67
|
+
|
|
68
|
+
if (!mainFn) {
|
|
69
|
+
// 如果没找到具名函数,尝试返回一个错误
|
|
70
|
+
throw new Error("未找到入口函数 (请定义 solution 或 twoSum 函数)");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return mainFn;
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
// 定义日志处理代理
|
|
77
|
+
let logHandler = (type: "log" | "error" | "warn", args: any[]) => {
|
|
78
|
+
const msg = args
|
|
79
|
+
.map((arg) =>
|
|
80
|
+
typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg),
|
|
81
|
+
)
|
|
82
|
+
.join(" ");
|
|
83
|
+
logs.push(msg); // 默认写入全局日志
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// 重写 console,使用代理
|
|
87
|
+
const customConsole = {
|
|
88
|
+
log: (...args: any[]) => logHandler("log", args),
|
|
89
|
+
error: (...args: any[]) => logHandler("error", args),
|
|
90
|
+
warn: (...args: any[]) => logHandler("warn", args),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// 获取用户函数
|
|
94
|
+
const getUserFn = new Function("console", wrapperCode);
|
|
95
|
+
const userFn = getUserFn(customConsole);
|
|
96
|
+
|
|
97
|
+
// 执行测试用例
|
|
98
|
+
for (const testCase of testCases) {
|
|
99
|
+
const caseLog: string[] = [];
|
|
100
|
+
const startTimeCase = performance.now();
|
|
101
|
+
let status: "passed" | "failed" | "error" = "error";
|
|
102
|
+
let output: any = undefined;
|
|
103
|
+
let errorMsg: string | undefined = undefined;
|
|
104
|
+
|
|
105
|
+
// 切换日志处理器到当前 case
|
|
106
|
+
logHandler = (type, args) => {
|
|
107
|
+
const msg = args
|
|
108
|
+
.map((arg) =>
|
|
109
|
+
typeof arg === "object"
|
|
110
|
+
? JSON.stringify(arg, null, 2)
|
|
111
|
+
: String(arg),
|
|
112
|
+
)
|
|
113
|
+
.join(" ");
|
|
114
|
+
|
|
115
|
+
caseLog.push(msg);
|
|
116
|
+
// 也保留到全局日志,带上前缀方便调试
|
|
117
|
+
// logs.push(`[Case ${testCase.id}] ${msg}`);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// 尝试安全调用
|
|
122
|
+
let result: any;
|
|
123
|
+
if (Array.isArray(testCase.input)) {
|
|
124
|
+
result = userFn(...testCase.input);
|
|
125
|
+
} else {
|
|
126
|
+
result = userFn(testCase.input);
|
|
127
|
+
}
|
|
128
|
+
output = result;
|
|
129
|
+
|
|
130
|
+
// 验证结果
|
|
131
|
+
let passed = false;
|
|
132
|
+
if (testCase.expected !== undefined) {
|
|
133
|
+
// 简单比较
|
|
134
|
+
try {
|
|
135
|
+
passed =
|
|
136
|
+
JSON.stringify(result) === JSON.stringify(testCase.expected);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
passed = result === testCase.expected;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
// 如果没有预期结果,只要不报错就算过?或者无法判断
|
|
142
|
+
passed = true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
status = passed ? "passed" : "failed";
|
|
146
|
+
} catch (e: any) {
|
|
147
|
+
status = "error";
|
|
148
|
+
errorMsg = e.message;
|
|
149
|
+
output = undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const endTimeCase = performance.now();
|
|
153
|
+
|
|
154
|
+
testResults.push({
|
|
155
|
+
id: testCase.id,
|
|
156
|
+
status,
|
|
157
|
+
input: testCase.input,
|
|
158
|
+
output,
|
|
159
|
+
expected: testCase.expected,
|
|
160
|
+
log: caseLog.join("\n"),
|
|
161
|
+
error: errorMsg,
|
|
162
|
+
executionTime: endTimeCase - startTimeCase,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const executionTime = performance.now() - startTime;
|
|
167
|
+
// 统计状态
|
|
168
|
+
const hasError = testResults.some((r) => r.status === "error");
|
|
169
|
+
const hasFailure = testResults.some((r) => r.status === "failed");
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
output: logs.join("\n"),
|
|
173
|
+
executionTime,
|
|
174
|
+
status: hasError || hasFailure ? "error" : "success", // 这里只代表是否全部通过
|
|
175
|
+
testResults,
|
|
176
|
+
};
|
|
177
|
+
} catch (error: any) {
|
|
178
|
+
const executionTime = performance.now() - startTime;
|
|
179
|
+
return {
|
|
180
|
+
output: "",
|
|
181
|
+
error: error.message || String(error),
|
|
182
|
+
executionTime,
|
|
183
|
+
status: "error",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Python 运行策略(使用 Pyodide)
|
|
190
|
+
*/
|
|
191
|
+
async function runPython(code: string): Promise<RunResult> {
|
|
192
|
+
const startTime = performance.now();
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// 提示:实际使用需要加载 Pyodide
|
|
196
|
+
// const pyodide = await loadPyodide();
|
|
197
|
+
|
|
198
|
+
// 临时返回,实际实现需要集成 Pyodide
|
|
199
|
+
return {
|
|
200
|
+
output: "Python 运行功能需要加载 Pyodide (暂未实现)",
|
|
201
|
+
executionTime: performance.now() - startTime,
|
|
202
|
+
status: "error",
|
|
203
|
+
error: "Pyodide 未加载",
|
|
204
|
+
};
|
|
205
|
+
} catch (error: any) {
|
|
206
|
+
return {
|
|
207
|
+
output: "",
|
|
208
|
+
error: error.message,
|
|
209
|
+
executionTime: performance.now() - startTime,
|
|
210
|
+
status: "error",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 远程运行策略(Java/C++/Go 等)
|
|
217
|
+
*/
|
|
218
|
+
async function runRemote(code: string, language: string): Promise<RunResult> {
|
|
219
|
+
const startTime = performance.now();
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
// 提示:需要配置远程编译服务 API
|
|
223
|
+
// 例如:Judge0 API, Piston API 等
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
output: `${language} 远程运行功能需要配置编译服务 (暂未实现)`,
|
|
227
|
+
executionTime: performance.now() - startTime,
|
|
228
|
+
status: "error",
|
|
229
|
+
error: "远程编译服务未配置",
|
|
230
|
+
};
|
|
231
|
+
} catch (error: any) {
|
|
232
|
+
return {
|
|
233
|
+
output: "",
|
|
234
|
+
error: error.message,
|
|
235
|
+
executionTime: performance.now() - startTime,
|
|
236
|
+
status: "error",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 运行策略映射
|
|
243
|
+
* ⭐ 新增自定义运行逻辑只需在这里添加
|
|
244
|
+
*/
|
|
245
|
+
export const RUNNER_STRATEGIES: Record<string, RunnerStrategy> = {
|
|
246
|
+
javascript: runJavaScript,
|
|
247
|
+
typescript: runJavaScript, // TypeScript 可以直接当 JS 运行(简化版)
|
|
248
|
+
python: runPython,
|
|
249
|
+
java: (code) => runRemote(code, "Java"),
|
|
250
|
+
cpp: (code) => runRemote(code, "C++"),
|
|
251
|
+
go: (code) => runRemote(code, "Go"),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 获取运行策略
|
|
256
|
+
*/
|
|
257
|
+
export function getRunnerStrategy(
|
|
258
|
+
language: string,
|
|
259
|
+
): RunnerStrategy | undefined {
|
|
260
|
+
return RUNNER_STRATEGIES[language];
|
|
261
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行策略 V2 - 整合后端支持
|
|
3
|
+
* 支持本地运行(JS/Python)和远程运行(其他语言)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RunResult, RunnerStrategy, TestCase } from "../types";
|
|
7
|
+
import { BackendRunner } from "../core/BackendRunner";
|
|
8
|
+
|
|
9
|
+
// 创建后端运行器实例(可配置)
|
|
10
|
+
let backendRunner: BackendRunner | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 配置后端运行器
|
|
14
|
+
*/
|
|
15
|
+
export function configureBackendRunner(config: {
|
|
16
|
+
apiUrl?: string;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
backendRunner = new BackendRunner(config);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 获取或创建后端运行器
|
|
25
|
+
*/
|
|
26
|
+
function getBackendRunner(): BackendRunner {
|
|
27
|
+
if (!backendRunner) {
|
|
28
|
+
backendRunner = new BackendRunner({
|
|
29
|
+
apiUrl: process.env.VITE_API_URL || "http://192.168.60.98:8080",
|
|
30
|
+
debug: process.env.NODE_ENV === "development",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return backendRunner;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* JavaScript/TypeScript 本地运行策略(改进版)
|
|
38
|
+
*/
|
|
39
|
+
async function runJavaScriptLocal(
|
|
40
|
+
code: string,
|
|
41
|
+
testCases?: TestCase[],
|
|
42
|
+
): Promise<RunResult> {
|
|
43
|
+
const startTime = performance.now();
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// 1. 无测试用例:Console 模式
|
|
47
|
+
if (!testCases || testCases.length === 0) {
|
|
48
|
+
const logs: string[] = [];
|
|
49
|
+
|
|
50
|
+
const customConsole = {
|
|
51
|
+
log: (...args: any[]) => {
|
|
52
|
+
logs.push(
|
|
53
|
+
args
|
|
54
|
+
.map((arg) => {
|
|
55
|
+
if (arg === undefined) return "undefined";
|
|
56
|
+
if (arg === null) return "null";
|
|
57
|
+
return typeof arg === "object"
|
|
58
|
+
? JSON.stringify(arg, null, 2)
|
|
59
|
+
: String(arg);
|
|
60
|
+
})
|
|
61
|
+
.join(" "),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
error: (...args: any[]) => logs.push("ERROR: " + args.join(" ")),
|
|
65
|
+
warn: (...args: any[]) => logs.push("WARN: " + args.join(" ")),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const fn = new Function("console", code);
|
|
69
|
+
fn(customConsole);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
output: logs.join("\n") || "(无输出)",
|
|
73
|
+
executionTime: performance.now() - startTime,
|
|
74
|
+
status: "success",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 2. 有测试用例:通过后端运行(获得更好的隔离和错误处理)
|
|
79
|
+
const backend = getBackendRunner();
|
|
80
|
+
return await backend.run("javascript", code, testCases);
|
|
81
|
+
} catch (error: any) {
|
|
82
|
+
return {
|
|
83
|
+
output: "",
|
|
84
|
+
error: error.message || String(error),
|
|
85
|
+
executionTime: performance.now() - startTime,
|
|
86
|
+
status: "error",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Python 运行策略(使用后端)
|
|
93
|
+
*/
|
|
94
|
+
async function runPython(
|
|
95
|
+
code: string,
|
|
96
|
+
testCases?: TestCase[],
|
|
97
|
+
): Promise<RunResult> {
|
|
98
|
+
try {
|
|
99
|
+
const backend = getBackendRunner();
|
|
100
|
+
return await backend.run("python", code, testCases);
|
|
101
|
+
} catch (error: any) {
|
|
102
|
+
return {
|
|
103
|
+
output: "",
|
|
104
|
+
error: `Python 运行失败: ${error.message}`,
|
|
105
|
+
executionTime: 0,
|
|
106
|
+
status: "error",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 远程运行策略(通过后端)
|
|
113
|
+
*/
|
|
114
|
+
async function runRemote(
|
|
115
|
+
code: string,
|
|
116
|
+
language: string,
|
|
117
|
+
testCases?: TestCase[],
|
|
118
|
+
): Promise<RunResult> {
|
|
119
|
+
try {
|
|
120
|
+
const backend = getBackendRunner();
|
|
121
|
+
return await backend.run(language, code, testCases);
|
|
122
|
+
} catch (error: any) {
|
|
123
|
+
return {
|
|
124
|
+
output: "",
|
|
125
|
+
error: `${language} 运行失败: ${error.message}`,
|
|
126
|
+
executionTime: 0,
|
|
127
|
+
status: "error",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 运行策略映射 V2
|
|
134
|
+
*/
|
|
135
|
+
export const RUNNER_STRATEGIES_V2: Record<string, RunnerStrategy> = {
|
|
136
|
+
// 本地运行
|
|
137
|
+
javascript: runJavaScriptLocal,
|
|
138
|
+
typescript: runJavaScriptLocal,
|
|
139
|
+
|
|
140
|
+
// 后端运行
|
|
141
|
+
python: runPython,
|
|
142
|
+
java: (code, testCases) => runRemote(code, "java", testCases),
|
|
143
|
+
cpp: (code, testCases) => runRemote(code, "cpp", testCases),
|
|
144
|
+
c: (code, testCases) => runRemote(code, "c", testCases),
|
|
145
|
+
go: (code, testCases) => runRemote(code, "go", testCases),
|
|
146
|
+
rust: (code, testCases) => runRemote(code, "rust", testCases),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取运行策略 V2
|
|
151
|
+
*/
|
|
152
|
+
export function getRunnerStrategyV2(
|
|
153
|
+
language: string,
|
|
154
|
+
): RunnerStrategy | undefined {
|
|
155
|
+
return RUNNER_STRATEGIES_V2[language];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 检查语言是否支持
|
|
160
|
+
*/
|
|
161
|
+
export function isLanguageSupported(language: string): boolean {
|
|
162
|
+
return language in RUNNER_STRATEGIES_V2;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 获取支持的语言列表
|
|
167
|
+
*/
|
|
168
|
+
export function getSupportedLanguages(): string[] {
|
|
169
|
+
return Object.keys(RUNNER_STRATEGIES_V2);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 检查后端健康状态
|
|
174
|
+
*/
|
|
175
|
+
export async function checkBackendHealth(): Promise<boolean> {
|
|
176
|
+
try {
|
|
177
|
+
const backend = getBackendRunner();
|
|
178
|
+
return await backend.healthCheck();
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|