st-comp 0.0.237 → 0.0.238

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.
Files changed (120) hide show
  1. package/components.d.ts +0 -1
  2. package/es/CustomFunction.cjs +1 -1
  3. package/es/CustomFunction.js +21 -21
  4. package/es/FactorWarning.cjs +1 -1
  5. package/es/FactorWarning.js +25 -25
  6. package/es/Kline.cjs +1 -1
  7. package/es/Kline.js +10 -10
  8. package/es/KlineBasic.cjs +1 -1
  9. package/es/KlineBasic.js +18 -18
  10. package/es/KlineConfig.cjs +1 -1
  11. package/es/KlineConfig.js +15 -15
  12. package/es/KlineNew.cjs +1 -1
  13. package/es/KlineNew.js +9 -9
  14. package/es/KlinePlus.cjs +1 -1
  15. package/es/KlinePlus.js +11 -11
  16. package/es/MonacoEditor.cjs +1 -1
  17. package/es/MonacoEditor.js +3 -31
  18. package/es/Pagination.cjs +1 -1
  19. package/es/Pagination.js +13 -13
  20. package/es/PasswordPrompt.cjs +1 -1
  21. package/es/PasswordPrompt.js +2 -2
  22. package/es/Table.cjs +1 -1
  23. package/es/Table.js +17 -17
  24. package/es/User.cjs +1 -1
  25. package/es/User.js +18 -18
  26. package/es/VarSelectDialog.cjs +3 -3
  27. package/es/VarSelectDialog.js +178 -122
  28. package/es/VarietyAutoComplete.cjs +1 -1
  29. package/es/VarietyAutoComplete.js +7 -7
  30. package/es/VarietySearch.cjs +17 -18
  31. package/es/VarietySearch.js +2592 -2705
  32. package/es/VarietyTextCopy.cjs +1 -1
  33. package/es/VarietyTextCopy.js +9 -9
  34. package/es/VirtualTable.cjs +1 -1
  35. package/es/VirtualTable.js +61 -61
  36. package/es/{_initCloneObject-3823a101.cjs → _initCloneObject-52b6a510.cjs} +1 -1
  37. package/es/{_initCloneObject-c34c65bc.js → _initCloneObject-eaef9418.js} +2 -2
  38. package/es/{config-provider-2182708a.cjs → config-provider-a584d81e.cjs} +1 -1
  39. package/es/{config-provider-06a63185.js → config-provider-b16efd62.js} +3 -3
  40. package/es/{dropdown-89b74bc9.cjs → dropdown-071c5d7e.cjs} +1 -1
  41. package/es/{dropdown-302f71e7.js → dropdown-a59bba73.js} +2 -2
  42. package/es/{el-autocomplete-b9a3054a.cjs → el-autocomplete-a07e9439.cjs} +1 -1
  43. package/es/{el-autocomplete-ed75a659.js → el-autocomplete-ba808eb6.js} +5 -5
  44. package/es/{el-button-d09ff85f.js → el-button-c95adb85.js} +3 -3
  45. package/es/{el-button-68baab7b.cjs → el-button-eec58cff.cjs} +1 -1
  46. package/es/{el-checkbox-64648e02.js → el-checkbox-7421ccd3.js} +3 -3
  47. package/es/{el-checkbox-b982e2ef.cjs → el-checkbox-c25236a6.cjs} +1 -1
  48. package/es/{el-dialog-6a80e3d8.js → el-dialog-41ab8417.js} +4 -4
  49. package/es/{el-dialog-ad7309e9.cjs → el-dialog-ae86edb8.cjs} +1 -1
  50. package/es/{el-form-item-4076e55f.cjs → el-form-item-c3fe189b.cjs} +1 -1
  51. package/es/{el-form-item-4eca95be.js → el-form-item-c53c374d.js} +5 -5
  52. package/es/{el-input-cae60510.js → el-input-2f75c4ba.js} +49 -49
  53. package/es/{el-input-172c49f8.cjs → el-input-7fd293af.cjs} +1 -1
  54. package/es/{el-input-number-c2e71528.cjs → el-input-number-22e21d16.cjs} +1 -1
  55. package/es/{el-input-number-c2499410.js → el-input-number-5193fe6d.js} +4 -4
  56. package/es/{el-loading-05826e64.cjs → el-loading-cfd86c15.cjs} +1 -1
  57. package/es/{el-loading-c738468d.js → el-loading-f6022062.js} +1 -1
  58. package/es/{el-menu-item-7f986598.cjs → el-menu-item-17dc717e.cjs} +1 -1
  59. package/es/{el-menu-item-f904f685.js → el-menu-item-7e881203.js} +4 -4
  60. package/es/{el-message-a86c0efa.cjs → el-message-5e6a6be9.cjs} +1 -1
  61. package/es/{el-message-box-05d8cf39.js → el-message-box-a93d2f6a.js} +9 -9
  62. package/es/{el-message-box-40ff2af5.cjs → el-message-box-c10adb52.cjs} +1 -1
  63. package/es/{el-message-0df23ae7.js → el-message-e544a8f5.js} +5 -5
  64. package/es/{el-overlay-cc9bc792.js → el-overlay-09ad71cd.js} +18 -18
  65. package/es/{el-overlay-d7a6e4a9.cjs → el-overlay-9e34965f.cjs} +1 -1
  66. package/es/{el-popconfirm-737a015b.cjs → el-popconfirm-70a976bf.cjs} +1 -1
  67. package/es/{el-popconfirm-a6f66a0e.js → el-popconfirm-81dcd202.js} +4 -4
  68. package/es/{el-popper-a38874f4.js → el-popper-b4f97157.js} +1 -1
  69. package/es/{el-popper-7ba87e05.cjs → el-popper-b6c99b28.cjs} +1 -1
  70. package/es/{el-segmented-51b1c797.js → el-segmented-b868d074.js} +2 -2
  71. package/es/{el-segmented-3fd66a0e.cjs → el-segmented-f8fce9ac.cjs} +1 -1
  72. package/es/{el-select-1b149fab.js → el-select-95627997.js} +8 -8
  73. package/es/{el-select-12f6deb7.cjs → el-select-d8d91db1.cjs} +1 -1
  74. package/es/{el-table-column-3e30ebae.js → el-table-column-376cd907.js} +9 -9
  75. package/es/{el-table-column-516a0ed9.cjs → el-table-column-c974cb96.cjs} +1 -1
  76. package/es/{el-tag-0a25efdf.js → el-tag-66cab138.js} +2 -2
  77. package/es/{el-tag-789f05d3.cjs → el-tag-a33c4b22.cjs} +1 -1
  78. package/es/{el-text-73d899ff.js → el-text-ac60d0f2.js} +1 -1
  79. package/es/{el-text-1470de46.cjs → el-text-c20a9f48.cjs} +1 -1
  80. package/es/{index-cebc7160.cjs → index-098c2447.cjs} +1 -1
  81. package/es/{index-c04f444f.cjs → index-11547a0c.cjs} +1 -1
  82. package/es/{index-4194c942.js → index-1f7d4f70.js} +1 -1
  83. package/es/{index-8de94a49.cjs → index-298075cf.cjs} +1 -1
  84. package/es/{index-94e43e0d.js → index-57672682.js} +2 -2
  85. package/es/{index-6806997d.js → index-844bdd85.js} +2 -2
  86. package/es/{index-6e967429.js → index-88546436.js} +2 -2
  87. package/es/{index-ee977f79.cjs → index-9b9ef5dd.cjs} +1 -1
  88. package/es/{index-87b4bf61.js → index-a871c3eb.js} +60 -75
  89. package/es/{index-ac98a4d8.js → index-bc8e277e.js} +12573 -12827
  90. package/es/{index-4f48940d.cjs → index-c108567d.cjs} +1 -1
  91. package/es/{index-2375023e.cjs → index-d725fef6.cjs} +137 -138
  92. package/es/{index-54d289d1.js → index-d91dc23f.js} +2 -2
  93. package/es/{index-42e59bf5.js → index-e5566b94.js} +1 -1
  94. package/es/{index-696b6a94.cjs → index-f3562b52.cjs} +1 -1
  95. package/es/{index-269b22da.cjs → index-f967d6c1.cjs} +1 -1
  96. package/es/{python-c67c8901.cjs → python-c27ba105.cjs} +2 -2
  97. package/es/{python-a914569a.js → python-ecde9ff2.js} +11 -39
  98. package/es/style.css +1 -1
  99. package/es/{use-form-common-props-47e50c10.js → use-form-common-props-815d48a6.js} +28 -28
  100. package/es/{use-form-common-props-344056f9.cjs → use-form-common-props-fd9b61a0.cjs} +1 -1
  101. package/es/{use-global-config-cf78ebac.cjs → use-global-config-30d7d8ce.cjs} +1 -1
  102. package/es/{use-global-config-f52caea0.js → use-global-config-b5e9d3d5.js} +4 -4
  103. package/es/{validator-3cad04b2.cjs → validator-1b8a6128.cjs} +1 -1
  104. package/es/{validator-94c04152.js → validator-764a9db0.js} +1 -1
  105. package/es/{zh-cn-aabfaa94.cjs → zh-cn-90317f62.cjs} +1 -1
  106. package/es/{zh-cn-4921961d.js → zh-cn-e963c628.js} +1 -1
  107. package/lib/bundle.js +1 -1
  108. package/lib/bundle.umd.cjs +225 -227
  109. package/lib/{index-c6d17ca2.js → index-f416420b.js} +31754 -32122
  110. package/lib/{python-09a6fcf8.js → python-766b93ca.js} +1 -1
  111. package/lib/style.css +1 -1
  112. package/package.json +1 -2
  113. package/packages/MonacoEditor/index.vue +70 -741
  114. package/packages/VarietySearch/components/AddTag/index.vue +86 -20
  115. package/packages/VarietySearch/components/FactorScreen/index.vue +12 -80
  116. package/packages/VarietySearch/components/FactorScreen/tools.js +0 -41
  117. package/src/main.ts +11 -16
  118. package/src/pages/MonacoEditor/index.vue +0 -1
  119. package/es/VarietySelect-2fd501da.cjs +0 -1
  120. package/es/VarietySelect-5a9dd50b.js +0 -68
@@ -1,18 +1,15 @@
1
1
  <script setup>
2
2
  import { debounce } from "st-func";
3
- import { InfoFilled } from "@element-plus/icons-vue";
4
3
  import { ref, onMounted, onUnmounted, nextTick, inject } from "vue";
5
- import { extractVariables } from "./tools";
6
4
  import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
7
5
  import "monaco-editor/esm/vs/basic-languages/python/python.contribution";
8
6
  import "monaco-editor/esm/vs/basic-languages/lua/lua.contribution";
9
- import VarietySelect from "../VarSelectDialog/VarietySelect.vue";
7
+ import { extractVariables } from "./tools";
10
8
 
11
9
  const { request } = inject("stConfig"); // 组件库全局配置
12
10
 
13
11
  const emit = defineEmits(["change"]);
14
12
  const props = defineProps({
15
- // 编辑器相关参数
16
13
  defaultValue: {
17
14
  type: String,
18
15
  default: "",
@@ -30,16 +27,6 @@ const props = defineProps({
30
27
  type: Boolean,
31
28
  default: false,
32
29
  },
33
- // 变量列表开关
34
- variableEnable: {
35
- type: Boolean,
36
- default: false,
37
- },
38
- // 变量列表类型
39
- useCase: {
40
- type: String,
41
- default: "1", // 1-选股, 2-回测
42
- },
43
30
  });
44
31
 
45
32
  let editorInstance = null;
@@ -52,209 +39,6 @@ const suggestionIndex = ref(-1);
52
39
  const suggestionBoxRef = ref(null);
53
40
  const isShowingSuggestions = ref(false);
54
41
 
55
- // 变量列表相关参数
56
- const variables = ref([]);
57
- const variableIndex = ref(-1);
58
- const variableBoxRef = ref(null);
59
- const isShowingVariables = ref(false);
60
- const currentVarName = ref(null);
61
- const currentFormatList = ref([]);
62
- const currentKeyword = ref("");
63
-
64
- // 变量数据源
65
- const varList = ref([]);
66
-
67
- // 格式化配置项的下拉框数据源
68
- const handleOptionsStrToArray = (str) => {
69
- const trimmedStr = str.replace(/^\[|\]$/g, "");
70
- const items = trimmedStr.split("],[");
71
-
72
- return items.map((item) => {
73
- const cleanItem = item.replace(/\[|\]/g, "");
74
- const [label, value] = cleanItem.split(",");
75
-
76
- return {
77
- label: label.trim(),
78
- value: value.trim(),
79
- };
80
- });
81
- };
82
-
83
- // 获取变量列表数据
84
- const fetchVariables = async () => {
85
- if (!props.variableEnable) return;
86
- try {
87
- const { body } = await request.post("/common/conf/queryAllBackVariables", { useCase: props.useCase });
88
- varList.value = body ?? [];
89
- } catch (error) {
90
- console.error("获取变量列表失败:", error);
91
- }
92
- };
93
-
94
- // [变量列表] 数据筛选
95
- const getVariables = debounce(async (keyword = "") => {
96
- if (!props.variableEnable) return;
97
-
98
- currentKeyword.value = keyword;
99
-
100
- // 根据关键字筛选变量名
101
- const filtered = varList.value.filter(({ varName }) => varName.toLowerCase().includes(keyword.toLowerCase()));
102
-
103
- if (filtered.length > 0) {
104
- showVariables(filtered);
105
- } else {
106
- hideVariables();
107
- }
108
- }, 200);
109
-
110
- // [变量列表] 展示
111
- const showVariables = async (options) => {
112
- if (!props.variableEnable) return;
113
- variables.value = options;
114
- variableIndex.value = options.length > 0 ? 0 : -1;
115
- isShowingVariables.value = true;
116
-
117
- // 如果有选中项,更新其格式列表
118
- if (variableIndex.value !== -1) {
119
- updateCurrentFormatList();
120
- }
121
-
122
- updateVariableBoxPosition();
123
- };
124
-
125
- // [变量列表] 隐藏
126
- const hideVariables = () => {
127
- if (!props.variableEnable) return;
128
- variables.value = [];
129
- variableIndex.value = -1;
130
- isShowingVariables.value = false;
131
- currentVarName.value = null;
132
- currentFormatList.value = [];
133
- };
134
-
135
- // [变量列表] 更新当前选中变量的格式列表
136
- const updateCurrentFormatList = () => {
137
- if (variableIndex.value === -1 || !variables.value[variableIndex.value]) return;
138
-
139
- const selectedVar = variables.value[variableIndex.value];
140
- currentVarName.value = selectedVar.varName;
141
- currentFormatList.value = selectedVar.formatList ?? [];
142
- };
143
-
144
- // [变量列表] 更新DOM位置
145
- const updateVariableBoxPosition = () => {
146
- if (!props.variableEnable) return;
147
- nextTick(() => {
148
- if (!editorInstance || !variableBoxRef.value) return;
149
- const position = editorInstance.getPosition();
150
- const cursorCoords = editorInstance.getScrolledVisiblePosition(position);
151
-
152
- if (cursorCoords) {
153
- const editorDom = editorInstance.getDomNode();
154
- const editorRect = editorDom.getBoundingClientRect();
155
-
156
- // 计算相对于编辑器容器的位置(在光标右侧,但比建议列表低一些)
157
- const left = cursorCoords.left + editorRect.left + 40;
158
- const top = cursorCoords.top + editorRect.top + cursorCoords.height + 5;
159
-
160
- variableBoxRef.value.style.left = `${left}px`;
161
- variableBoxRef.value.style.top = `${top}px`;
162
- }
163
- });
164
- };
165
-
166
- // [变量列表] 插入变量内容
167
- const insertVariableContent = (configList) => {
168
- if (!editorInstance) return;
169
-
170
- // 1.校验是否填写完整 + 生成输出值
171
- const result = [];
172
- for (let index = 0; index < configList.length; index++) {
173
- const item = configList[index];
174
- switch (item.vtype) {
175
- case "text": {
176
- result.push(item.param);
177
- break;
178
- }
179
- default: {
180
- if (!item.modelValue) {
181
- ElMessage.error("请检查插入格式内是否填写完整");
182
- return;
183
- }
184
- let formatValue = item.modelValue;
185
- if (item.prefix) formatValue = `${item.prefix}${formatValue}`;
186
- if (item.suffix) formatValue = `${formatValue}${item.suffix}`;
187
- result.push(formatValue);
188
- break;
189
- }
190
- }
191
- }
192
-
193
- // 2.插入内容
194
- const content = result.join("_");
195
- const position = editorInstance.getPosition();
196
-
197
- const currentValue = editorInstance.getValue();
198
- const lines = currentValue.split("\n");
199
-
200
- // 如果光标位置有效
201
- if (position.lineNumber <= lines.length) {
202
- const lineIndex = position.lineNumber - 1;
203
- const line = lines[lineIndex];
204
- const columnIndex = position.column - 1;
205
-
206
- // 获取光标前后的字符
207
- const prevChar = columnIndex > 0 ? line[columnIndex - 1] : "";
208
- const nextChar = columnIndex < line.length ? line[columnIndex] : "";
209
-
210
- // 检测是否需要添加空格
211
- let contentToInsert = content;
212
-
213
- // 如果前面有内容且不是空格或行首,在前面添加空格
214
- if (prevChar && prevChar !== " " && !/[\s({[ ]/.test(prevChar)) {
215
- contentToInsert = " " + contentToInsert;
216
- }
217
-
218
- // 如果后面有内容且不是空格或行尾,在后面添加空格
219
- if (nextChar && nextChar !== " " && !/[\s)}\]]/.test(nextChar)) {
220
- contentToInsert = contentToInsert + " ";
221
- }
222
-
223
- // 获取当前光标前的单词范围(用于替换)
224
- const wordUntilPosition = editorInstance.getModel().getWordUntilPosition(position);
225
-
226
- // 如果当前有正在输入的变量名,替换整个单词
227
- const startPosition = {
228
- lineNumber: position.lineNumber,
229
- column: wordUntilPosition.startColumn,
230
- };
231
-
232
- const endPosition = {
233
- lineNumber: position.lineNumber,
234
- column: position.column,
235
- };
236
-
237
- // 执行编辑操作
238
- editorInstance.executeEdits("variable-insert", [
239
- {
240
- range: new monaco.Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column),
241
- text: contentToInsert,
242
- forceMoveMarkers: true,
243
- },
244
- ]);
245
-
246
- // 将光标移动到插入内容之后
247
- const newColumn = startPosition.column + contentToInsert.length;
248
- editorInstance.setPosition({
249
- lineNumber: position.lineNumber,
250
- column: newColumn,
251
- });
252
- editorInstance.focus();
253
- }
254
-
255
- // 插入后保持变量列表显示(不关闭)
256
- };
257
-
258
42
  // [建议列表] 数据获取
259
43
  const getSuggestions = debounce(async (keyword = "") => {
260
44
  if (!props.suggestionEnable) return;
@@ -271,7 +55,6 @@ const getSuggestions = debounce(async (keyword = "") => {
271
55
  hideSuggestions();
272
56
  }
273
57
  }, 200);
274
-
275
58
  // [建议列表] 展示
276
59
  const showSuggestions = async (options) => {
277
60
  if (!props.suggestionEnable) return;
@@ -280,7 +63,6 @@ const showSuggestions = async (options) => {
280
63
  isShowingSuggestions.value = true;
281
64
  updateSuggestionBoxPosition();
282
65
  };
283
-
284
66
  // [建议列表] 隐藏
285
67
  const hideSuggestions = () => {
286
68
  if (!props.suggestionEnable) return;
@@ -288,7 +70,6 @@ const hideSuggestions = () => {
288
70
  suggestionIndex.value = -1;
289
71
  isShowingSuggestions.value = false;
290
72
  };
291
-
292
73
  // [建议列表] 更新DOM位置
293
74
  const updateSuggestionBoxPosition = () => {
294
75
  if (!props.suggestionEnable) return;
@@ -301,7 +82,8 @@ const updateSuggestionBoxPosition = () => {
301
82
  const editorDom = editorInstance.getDomNode();
302
83
  const editorRect = editorDom.getBoundingClientRect();
303
84
 
304
- const left = cursorCoords.left + editorRect.left + 40;
85
+ // 计算相对于编辑器容器的位置
86
+ const left = cursorCoords.left + editorRect.left + 40; // 在光标右侧
305
87
  const top = cursorCoords.top + editorRect.top + cursorCoords.height;
306
88
 
307
89
  suggestionBoxRef.value.style.left = `${left}px`;
@@ -309,7 +91,6 @@ const updateSuggestionBoxPosition = () => {
309
91
  }
310
92
  });
311
93
  };
312
-
313
94
  // [建议列表] 插入内容到编辑器
314
95
  const insertSelectedSuggestion = () => {
315
96
  if (!props.suggestionEnable) return;
@@ -321,6 +102,7 @@ const insertSelectedSuggestion = () => {
321
102
  if (varArray.length) {
322
103
  const str = varArray.reduce((result, item, index, arry) => {
323
104
  result += `${item}=`;
105
+ // 如果不是最后一个, 就需要加个逗号
324
106
  if (index < arry.length - 1) result += ",";
325
107
  return result;
326
108
  }, "");
@@ -366,49 +148,9 @@ const insertSelectedSuggestion = () => {
366
148
 
367
149
  hideSuggestions();
368
150
  };
369
-
370
- // [建议列表] 选项自动滚动
371
- const scrollToSelectedSuggestion = () => {
151
+ // [建议列表] 随用户内容输入变动检查建议项
152
+ const checkForSuggestions = async () => {
372
153
  if (!props.suggestionEnable) return;
373
- if (!suggestionBoxRef.value || suggestionIndex.value === -1) return;
374
-
375
- const suggestionList = suggestionBoxRef.value.querySelector(".suggestion-list");
376
- const selectedItem = suggestionBoxRef.value.querySelector(".suggestion-item.selected");
377
-
378
- if (suggestionList && selectedItem) {
379
- const listRect = suggestionList.getBoundingClientRect();
380
- const itemRect = selectedItem.getBoundingClientRect();
381
-
382
- if (itemRect.top < listRect.top) {
383
- suggestionList.scrollTop -= listRect.top - itemRect.top;
384
- } else if (itemRect.bottom > listRect.bottom) {
385
- suggestionList.scrollTop += itemRect.bottom - listRect.bottom;
386
- }
387
- }
388
- };
389
-
390
- // [变量列表] 选项自动滚动
391
- const scrollToSelectedVariable = () => {
392
- if (!props.variableEnable) return;
393
- if (!variableBoxRef.value || variableIndex.value === -1) return;
394
-
395
- const variableList = variableBoxRef.value.querySelector(".variable-list");
396
- const selectedItem = variableBoxRef.value.querySelector(".variable-item.selected");
397
-
398
- if (variableList && selectedItem) {
399
- const listRect = variableList.getBoundingClientRect();
400
- const itemRect = selectedItem.getBoundingClientRect();
401
-
402
- if (itemRect.top < listRect.top) {
403
- variableList.scrollTop -= listRect.top - itemRect.top;
404
- } else if (itemRect.bottom > listRect.bottom) {
405
- variableList.scrollTop += itemRect.bottom - listRect.bottom;
406
- }
407
- }
408
- };
409
-
410
- // 检查当前输入内容,决定显示建议列表还是变量列表
411
- const checkForCompletions = async () => {
412
154
  const position = editorInstance.getPosition();
413
155
  const model = editorInstance.getModel();
414
156
  const lineContent = model.getLineContent(position.lineNumber);
@@ -416,140 +158,104 @@ const checkForCompletions = async () => {
416
158
  const textBeforeCursor = lineContent.substring(0, position.column - 1);
417
159
  const lastHashIndex = textBeforeCursor.lastIndexOf("#");
418
160
 
419
- // 如果是#触发,显示建议列表
420
161
  if (lastHashIndex !== -1) {
421
- // 如果变量列表正在显示,先隐藏
422
- if (isShowingVariables.value) hideVariables();
423
-
424
162
  const keyword = textBeforeCursor.substring(lastHashIndex + 1);
425
163
  if (lastCursorPosition && (lastCursorPosition.lineNumber !== position.lineNumber || lastCursorPosition.column !== position.column)) {
426
164
  hideSuggestions();
427
165
  }
428
166
  lastCursorPosition = { ...position };
429
167
 
430
- if (props.suggestionEnable) {
431
- getSuggestions(keyword);
432
- }
168
+ getSuggestions(keyword);
169
+ } else {
170
+ hideSuggestions();
433
171
  }
434
- // 否则检查变量列表
435
- else {
436
- // 如果建议列表正在显示,先隐藏
437
- if (isShowingSuggestions.value) hideSuggestions();
172
+ };
173
+ // [建议列表] 选项自动滚动
174
+ const scrollToSelectedSuggestion = () => {
175
+ if (!props.suggestionEnable) return;
176
+ if (!suggestionBoxRef.value || suggestionIndex.value === -1) return;
438
177
 
439
- // 获取光标前的单词作为关键字
440
- const wordUntilPosition = model.getWordUntilPosition(position);
441
- const keyword = wordUntilPosition.word;
178
+ const suggestionList = suggestionBoxRef.value.querySelector(".suggestion-list");
179
+ const selectedItem = suggestionBoxRef.value.querySelector(".suggestion-item.selected");
442
180
 
443
- if (lastCursorPosition && (lastCursorPosition.lineNumber !== position.lineNumber || lastCursorPosition.column !== position.column)) {
444
- hideVariables();
445
- }
446
- lastCursorPosition = { ...position };
181
+ if (suggestionList && selectedItem) {
182
+ const listRect = suggestionList.getBoundingClientRect();
183
+ const itemRect = selectedItem.getBoundingClientRect();
447
184
 
448
- if (props.variableEnable && keyword.length > 0) {
449
- getVariables(keyword);
450
- } else if (keyword.length === 0) {
451
- hideVariables();
185
+ // 如果选中项在可视区域上方
186
+ if (itemRect.top < listRect.top) {
187
+ suggestionList.scrollTop -= listRect.top - itemRect.top;
188
+ }
189
+ // 如果选中项在可视区域下方
190
+ else if (itemRect.bottom > listRect.bottom) {
191
+ suggestionList.scrollTop += itemRect.bottom - listRect.bottom;
452
192
  }
453
193
  }
454
194
  };
455
195
 
456
196
  // [代码编辑器] 键盘事件
457
197
  const handleKeyDown = (e) => {
458
- // 变量列表键盘导航
459
- if (isShowingVariables.value) {
460
- switch (e.code) {
461
- case "ArrowUp": {
462
- e.preventDefault();
463
- e.stopPropagation();
464
- variableIndex.value = variableIndex.value <= 0 ? variables.value.length - 1 : variableIndex.value - 1;
465
- updateCurrentFormatList();
466
- nextTick(() => scrollToSelectedVariable());
467
- break;
468
- }
469
- case "ArrowDown": {
470
- e.preventDefault();
471
- e.stopPropagation();
472
- variableIndex.value = variableIndex.value >= variables.value.length - 1 ? 0 : variableIndex.value + 1;
473
- updateCurrentFormatList();
474
- nextTick(() => scrollToSelectedVariable());
475
- break;
476
- }
477
- case "Escape": {
478
- e.preventDefault();
479
- e.stopPropagation();
480
- hideVariables();
481
- break;
482
- }
483
- default:
484
- break;
198
+ if (!isShowingSuggestions.value) return;
199
+ switch (e.code) {
200
+ case "ArrowUp": {
201
+ e.preventDefault();
202
+ e.stopPropagation();
203
+ suggestionIndex.value = suggestionIndex.value <= 0 ? suggestions.value.length - 1 : suggestionIndex.value - 1;
204
+ nextTick(() => scrollToSelectedSuggestion());
205
+ break;
485
206
  }
486
- }
487
-
488
- // 建议列表键盘导航
489
- if (isShowingSuggestions.value) {
490
- switch (e.code) {
491
- case "ArrowUp": {
492
- e.preventDefault();
493
- e.stopPropagation();
494
- suggestionIndex.value = suggestionIndex.value <= 0 ? suggestions.value.length - 1 : suggestionIndex.value - 1;
495
- nextTick(() => scrollToSelectedSuggestion());
496
- break;
497
- }
498
- case "ArrowDown": {
499
- e.preventDefault();
500
- e.stopPropagation();
501
- suggestionIndex.value = suggestionIndex.value >= suggestions.value.length - 1 ? 0 : suggestionIndex.value + 1;
502
- nextTick(() => scrollToSelectedSuggestion());
503
- break;
504
- }
505
- case "Enter": {
506
- if (isShowingSuggestions.value) {
507
- e.preventDefault();
508
- e.stopPropagation();
509
- insertSelectedSuggestion();
510
- }
511
- break;
512
- }
513
- case "Escape": {
514
- e.preventDefault();
515
- e.stopPropagation();
516
- if (isShowingSuggestions.value) hideSuggestions();
517
- if (isShowingVariables.value) hideVariables();
518
- break;
519
- }
520
- default:
521
- break;
207
+ case "ArrowDown": {
208
+ e.preventDefault();
209
+ e.stopPropagation();
210
+ suggestionIndex.value = suggestionIndex.value >= suggestions.value.length - 1 ? 0 : suggestionIndex.value + 1;
211
+ nextTick(() => scrollToSelectedSuggestion());
212
+ break;
213
+ }
214
+ case "ArrowLeft": {
215
+ e.preventDefault();
216
+ e.stopPropagation();
217
+ break;
522
218
  }
219
+ case "ArrowRight": {
220
+ e.preventDefault();
221
+ e.stopPropagation();
222
+ break;
223
+ }
224
+ case "Enter": {
225
+ e.preventDefault();
226
+ e.stopPropagation();
227
+ insertSelectedSuggestion();
228
+ break;
229
+ }
230
+ case "Escape": {
231
+ e.preventDefault();
232
+ e.stopPropagation();
233
+ hideSuggestions();
234
+ break;
235
+ }
236
+ default:
237
+ break;
523
238
  }
524
239
  };
525
-
526
240
  // [代码编辑器] 内容变化回调
527
241
  const handleContentChange = (event) => {
528
242
  if (!editorInstance) return;
529
243
  emit("change", editorInstance.getValue());
530
- checkForCompletions();
244
+ checkForSuggestions();
531
245
  };
532
-
533
246
  // [代码编辑器] 光标变化回调
534
247
  const handleCursorPositionChange = (event) => {
535
248
  if (isShowingSuggestions.value) {
536
249
  updateSuggestionBoxPosition();
537
250
  }
538
- if (isShowingVariables.value) {
539
- updateVariableBoxPosition();
540
- }
541
251
  };
542
252
 
543
253
  // [document] 全局监听的点击事件
544
254
  const handleClickOutside = (event) => {
545
- // 如果点击了建议列表外面的区域,就自动关闭
255
+ // 如果点击了建议列表外面的区域, 就自动关闭
546
256
  if (suggestionBoxRef.value && !suggestionBoxRef.value.contains(event.target)) {
547
257
  hideSuggestions();
548
258
  }
549
- // 如果点击了变量列表外面的区域,就自动关闭
550
- if (variableBoxRef.value && !variableBoxRef.value.contains(event.target)) {
551
- hideVariables();
552
- }
553
259
  };
554
260
 
555
261
  onMounted(() => {
@@ -558,22 +264,15 @@ onMounted(() => {
558
264
  language: props.language,
559
265
  theme: props.theme,
560
266
  });
561
-
562
267
  editorInstance.onKeyDown(handleKeyDown);
563
268
  editorInstance.onDidChangeModelContent(handleContentChange);
564
269
  editorInstance.onDidChangeCursorPosition(handleCursorPositionChange);
565
-
566
270
  document.addEventListener("click", handleClickOutside);
567
-
568
- // 获取变量列表数据
569
- fetchVariables();
570
271
  });
571
-
572
272
  onUnmounted(() => {
573
273
  editorInstance?.dispose();
574
274
  document.removeEventListener("click", handleClickOutside);
575
275
  });
576
-
577
276
  defineExpose({
578
277
  resize: () => {
579
278
  editorInstance.layout();
@@ -596,9 +295,11 @@ defineExpose({
596
295
  const model = editorInstance.getModel();
597
296
  if (!model) return;
598
297
 
298
+ // 获取最后一行
599
299
  const lineCount = model.getLineCount();
600
300
  const lastLine = model.getLineContent(lineCount);
601
301
 
302
+ // 设置光标位置到最后一行末尾
602
303
  const position = {
603
304
  lineNumber: lineCount,
604
305
  column: lastLine.length + 1,
@@ -606,6 +307,8 @@ defineExpose({
606
307
 
607
308
  editorInstance.setPosition(position);
608
309
  editorInstance.focus();
310
+
311
+ // 滚动到光标位置
609
312
  editorInstance.revealPositionInCenter(position);
610
313
  },
611
314
  });
@@ -618,7 +321,6 @@ defineExpose({
618
321
  ref="editorContainer"
619
322
  class="editor-container"
620
323
  />
621
-
622
324
  <!-- 建议列表 -->
623
325
  <Teleport
624
326
  to="body"
@@ -648,157 +350,9 @@ defineExpose({
648
350
  <div class="suggestion-footer">使用 ↑↓ 选择,Enter 确认,Esc 取消</div>
649
351
  </div>
650
352
  </Teleport>
651
-
652
- <!-- 变量列表 -->
653
- <Teleport
654
- to="body"
655
- v-if="isShowingVariables && variables.length > 0"
656
- >
657
- <div
658
- ref="variableBoxRef"
659
- class="variable-box"
660
- >
661
- <div class="variable-header">
662
- <span class="variable-title">变量列表</span>
663
- <span class="variable-count">{{ variables.length }} 项</span>
664
- <span
665
- class="variable-keyword"
666
- v-if="currentKeyword"
667
- >"{{ currentKeyword }}"</span
668
- >
669
- </div>
670
-
671
- <div class="variable-content">
672
- <!-- 左侧变量名列表 -->
673
- <div class="variable-list">
674
- <div
675
- v-for="(item, index) in variables"
676
- :key="item.varName"
677
- :class="['variable-item', { selected: index === variableIndex }]"
678
- @click="
679
- variableIndex = index;
680
- updateCurrentFormatList();
681
- "
682
- >
683
- <span class="variable-name">{{ item.varName }}</span>
684
- </div>
685
- </div>
686
-
687
- <!-- 右侧格式配置区域 -->
688
- <div
689
- class="format-section"
690
- v-if="currentFormatList.length"
691
- >
692
- <div
693
- class="format-item"
694
- v-for="(formatItem, formatIndex) in currentFormatList"
695
- :key="formatIndex"
696
- >
697
- <div class="format-header">
698
- <span>格式{{ formatIndex + 1 }}</span>
699
- <el-tooltip
700
- effect="dark"
701
- placement="top-start"
702
- >
703
- <template #content>
704
- <div style="max-width: 820px">
705
- <span style="white-space: pre-line">{{ formatItem.tip }}</span>
706
- </div>
707
- </template>
708
- <el-icon><InfoFilled /></el-icon>
709
- </el-tooltip>
710
- </div>
711
-
712
- <!-- 配置项 -->
713
- <div class="config-list">
714
- <template v-for="(config, configIndex) in formatItem.configList">
715
- <div class="config-item">
716
- <!-- 类型: 固定值 -->
717
- <template v-if="config.vtype === 'text'">
718
- <span>{{ config.param }}</span>
719
- </template>
720
- <template v-if="config.vtype === 'inputVariety'">
721
- <VarietySelect
722
- size="small"
723
- :placeholder="config.param"
724
- :selectClearEnable="false"
725
- :labelShowEnable="false"
726
- @select="({ name, code }) => (config.modelValue = code)"
727
- @change="(value) => (config.modelValue = value)"
728
- style="width: 100px"
729
- />
730
- </template>
731
- <template v-if="config.vtype === 'input'">
732
- <span v-if="config.prefix">{{ config.prefix }}</span>
733
- <el-input
734
- v-model="config.modelValue"
735
- :placeholder="config.param"
736
- size="small"
737
- />
738
- <span v-if="config.suffix">{{ config.suffix }}</span>
739
- </template>
740
- <template v-if="config.vtype === 'select'">
741
- <el-select
742
- v-model="config.modelValue"
743
- :placeholder="config.param"
744
- clearable
745
- size="small"
746
- >
747
- <el-option
748
- v-for="item in handleOptionsStrToArray(config.optionsStr)"
749
- :key="item.value"
750
- :label="item.label"
751
- :value="item.value"
752
- />
753
- </el-select>
754
- </template>
755
- </div>
756
- <span
757
- v-if="configIndex !== formatItem.configList.length - 1"
758
- class="separator"
759
- >_</span
760
- >
761
- </template>
762
- </div>
763
-
764
- <!-- 插入变量按钮 -->
765
- <el-button
766
- type="primary"
767
- size="small"
768
- class="insert-btn"
769
- @click="insertVariableContent(formatItem.configList)"
770
- >
771
- 插入变量
772
- </el-button>
773
- </div>
774
- </div>
775
- </div>
776
-
777
- <div class="variable-footer">使用 ↑↓ 选择变量,点击插入按钮确认,Esc 取消</div>
778
- </div>
779
- </Teleport>
780
353
  </div>
781
354
  </template>
782
355
 
783
- <style lang="scss">
784
- /* 变量列表下拉框和tooltip的全局样式 */
785
- .variable-select-popper,
786
- .variable-variety-popper,
787
- .variable-tooltip-popper {
788
- z-index: 100000 !important;
789
- }
790
-
791
- /* 确保所有 Element Plus 的弹层都有足够高的层级 */
792
- .el-popper {
793
- z-index: 100000 !important;
794
- }
795
-
796
- /* 如果是旧版本 Element Plus */
797
- .el-select__popper,
798
- .el-tooltip__popper {
799
- z-index: 100000 !important;
800
- }
801
- </style>
802
356
  <style scoped lang="scss">
803
357
  .editor-wrapper {
804
358
  position: relative;
@@ -809,8 +363,6 @@ defineExpose({
809
363
  width: 100%;
810
364
  height: 100%;
811
365
  }
812
-
813
- // 建议列表样式
814
366
  .suggestion-box {
815
367
  position: fixed;
816
368
  background: #1e1e1e;
@@ -835,7 +387,6 @@ defineExpose({
835
387
  transform: rotate(45deg);
836
388
  z-index: -1;
837
389
  }
838
-
839
390
  .suggestion-header {
840
391
  display: flex;
841
392
  justify-content: space-between;
@@ -855,7 +406,6 @@ defineExpose({
855
406
  opacity: 0.8;
856
407
  }
857
408
  }
858
-
859
409
  .suggestion-list {
860
410
  max-height: 200px;
861
411
  overflow-y: auto;
@@ -908,7 +458,6 @@ defineExpose({
908
458
  }
909
459
  }
910
460
  }
911
-
912
461
  .suggestion-footer {
913
462
  padding: 6px 12px;
914
463
  background: #2d2d30;
@@ -918,224 +467,4 @@ defineExpose({
918
467
  text-align: center;
919
468
  }
920
469
  }
921
- // 变量列表样式
922
- .variable-box {
923
- position: fixed;
924
- background: #1e1e1e;
925
- border: 1px solid #444;
926
- border-radius: 6px;
927
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6);
928
- z-index: 9999;
929
- min-width: 600px;
930
- max-width: 900px;
931
- overflow: hidden;
932
-
933
- &::before {
934
- content: "";
935
- position: absolute;
936
- top: -6px;
937
- left: 12px;
938
- width: 12px;
939
- height: 12px;
940
- background: #1e1e1e;
941
- border-left: 1px solid #444;
942
- border-top: 1px solid #444;
943
- transform: rotate(45deg);
944
- z-index: -1;
945
- }
946
-
947
- .variable-header {
948
- display: flex;
949
- justify-content: space-between;
950
- align-items: center;
951
- padding: 8px 12px;
952
- background: #2d2d30;
953
- border-bottom: 1px solid #444;
954
- font-size: 12px;
955
- color: #969696;
956
-
957
- .variable-title {
958
- font-weight: 600;
959
- }
960
-
961
- .variable-count {
962
- font-size: 11px;
963
- opacity: 0.8;
964
- }
965
-
966
- .variable-keyword {
967
- font-size: 11px;
968
- color: #007acc;
969
- background: #09477133;
970
- padding: 2px 6px;
971
- border-radius: 4px;
972
- }
973
- }
974
-
975
- .variable-content {
976
- display: flex;
977
- max-height: 300px;
978
- overflow: hidden;
979
-
980
- .variable-list {
981
- width: 160px;
982
- border-right: 1px solid #444;
983
- overflow-y: auto;
984
- padding: 4px 0;
985
-
986
- &::-webkit-scrollbar {
987
- width: 6px;
988
- }
989
-
990
- &::-webkit-scrollbar-track {
991
- background: transparent;
992
- }
993
-
994
- &::-webkit-scrollbar-thumb {
995
- background: #424242;
996
- border-radius: 3px;
997
- }
998
-
999
- .variable-item {
1000
- padding: 8px 12px;
1001
- cursor: pointer;
1002
- border-left: 3px solid transparent;
1003
- font-size: 12px;
1004
- color: #d4d4d4;
1005
-
1006
- &:hover {
1007
- background: #2a2d2e;
1008
- border-left-color: #007acc;
1009
- }
1010
-
1011
- &.selected {
1012
- background: #094771;
1013
- border-left-color: #007acc;
1014
- color: #ffffff;
1015
- font-weight: 500;
1016
- }
1017
- }
1018
- }
1019
-
1020
- .format-section {
1021
- flex: 1;
1022
- padding: 12px;
1023
- overflow-y: auto;
1024
- max-height: 300px;
1025
-
1026
- &::-webkit-scrollbar {
1027
- width: 6px;
1028
- }
1029
-
1030
- &::-webkit-scrollbar-track {
1031
- background: transparent;
1032
- }
1033
-
1034
- &::-webkit-scrollbar-thumb {
1035
- background: #424242;
1036
- border-radius: 3px;
1037
- }
1038
-
1039
- .format-item {
1040
- margin-bottom: 16px;
1041
- padding: 8px;
1042
- background: #2d2d30;
1043
- border-radius: 4px;
1044
-
1045
- &:last-child {
1046
- margin-bottom: 0;
1047
- }
1048
-
1049
- .format-header {
1050
- display: flex;
1051
- align-items: center;
1052
- gap: 6px;
1053
- margin-bottom: 8px;
1054
- font-size: 11px;
1055
- color: #969696;
1056
-
1057
- .el-icon {
1058
- cursor: help;
1059
- font-size: 14px;
1060
-
1061
- &:hover {
1062
- color: #007acc;
1063
- }
1064
- }
1065
- }
1066
-
1067
- .config-list {
1068
- display: flex;
1069
- align-items: center;
1070
- flex-wrap: wrap;
1071
- gap: 4px 8px;
1072
- margin-bottom: 8px;
1073
-
1074
- .config-item {
1075
- display: flex;
1076
- align-items: center;
1077
- gap: 2px;
1078
-
1079
- span {
1080
- font-size: 12px;
1081
- color: #d4d4d4;
1082
- }
1083
-
1084
- :deep(.el-input) {
1085
- width: 100px;
1086
-
1087
- .el-input__wrapper {
1088
- background: #3c3c3c;
1089
- box-shadow: none;
1090
- padding: 1px 8px;
1091
-
1092
- input {
1093
- color: #d4d4d4;
1094
- }
1095
- }
1096
- }
1097
-
1098
- :deep(.el-select) {
1099
- width: 100px;
1100
-
1101
- .el-select__wrapper {
1102
- background: #3c3c3c;
1103
- box-shadow: none;
1104
- min-height: 24px;
1105
- padding: 0 8px;
1106
-
1107
- .el-select__placeholder {
1108
- color: #969696;
1109
- }
1110
-
1111
- .el-select__selected-item {
1112
- color: #d4d4d4;
1113
- }
1114
- }
1115
- }
1116
- }
1117
-
1118
- .separator {
1119
- color: #969696;
1120
- font-size: 12px;
1121
- }
1122
- }
1123
-
1124
- .insert-btn {
1125
- width: 100%;
1126
- margin-top: 4px;
1127
- }
1128
- }
1129
- }
1130
- }
1131
-
1132
- .variable-footer {
1133
- padding: 6px 12px;
1134
- background: #2d2d30;
1135
- border-top: 1px solid #444;
1136
- font-size: 11px;
1137
- color: #969696;
1138
- text-align: center;
1139
- }
1140
- }
1141
470
  </style>