st-comp 0.0.239 → 0.0.242

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 (121) hide show
  1. package/components.d.ts +1 -0
  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 +31 -3
  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 +122 -178
  28. package/es/VarietyAutoComplete.cjs +1 -1
  29. package/es/VarietyAutoComplete.js +7 -7
  30. package/es/VarietySearch.cjs +20 -19
  31. package/es/VarietySearch.js +2943 -2740
  32. package/es/VarietySelect-2fd501da.cjs +1 -0
  33. package/es/VarietySelect-5a9dd50b.js +68 -0
  34. package/es/VarietyTextCopy.cjs +1 -1
  35. package/es/VarietyTextCopy.js +9 -9
  36. package/es/VirtualTable.cjs +1 -1
  37. package/es/VirtualTable.js +61 -61
  38. package/es/{_initCloneObject-52b6a510.cjs → _initCloneObject-3823a101.cjs} +1 -1
  39. package/es/{_initCloneObject-eaef9418.js → _initCloneObject-c34c65bc.js} +2 -2
  40. package/es/{config-provider-b16efd62.js → config-provider-06a63185.js} +3 -3
  41. package/es/{config-provider-a584d81e.cjs → config-provider-2182708a.cjs} +1 -1
  42. package/es/{dropdown-a59bba73.js → dropdown-302f71e7.js} +2 -2
  43. package/es/{dropdown-071c5d7e.cjs → dropdown-89b74bc9.cjs} +1 -1
  44. package/es/{el-autocomplete-a07e9439.cjs → el-autocomplete-b9a3054a.cjs} +1 -1
  45. package/es/{el-autocomplete-ba808eb6.js → el-autocomplete-ed75a659.js} +5 -5
  46. package/es/{el-button-eec58cff.cjs → el-button-68baab7b.cjs} +1 -1
  47. package/es/{el-button-c95adb85.js → el-button-d09ff85f.js} +3 -3
  48. package/es/{el-checkbox-7421ccd3.js → el-checkbox-64648e02.js} +3 -3
  49. package/es/{el-checkbox-c25236a6.cjs → el-checkbox-b982e2ef.cjs} +1 -1
  50. package/es/{el-dialog-41ab8417.js → el-dialog-6a80e3d8.js} +4 -4
  51. package/es/{el-dialog-ae86edb8.cjs → el-dialog-ad7309e9.cjs} +1 -1
  52. package/es/{el-form-item-c3fe189b.cjs → el-form-item-4076e55f.cjs} +1 -1
  53. package/es/{el-form-item-c53c374d.js → el-form-item-4eca95be.js} +5 -5
  54. package/es/{el-input-7fd293af.cjs → el-input-172c49f8.cjs} +1 -1
  55. package/es/{el-input-2f75c4ba.js → el-input-cae60510.js} +49 -49
  56. package/es/{el-input-number-5193fe6d.js → el-input-number-c2499410.js} +4 -4
  57. package/es/{el-input-number-22e21d16.cjs → el-input-number-c2e71528.cjs} +1 -1
  58. package/es/{el-loading-cfd86c15.cjs → el-loading-05826e64.cjs} +1 -1
  59. package/es/{el-loading-f6022062.js → el-loading-c738468d.js} +1 -1
  60. package/es/{el-menu-item-17dc717e.cjs → el-menu-item-7f986598.cjs} +1 -1
  61. package/es/{el-menu-item-7e881203.js → el-menu-item-f904f685.js} +4 -4
  62. package/es/{el-message-e544a8f5.js → el-message-0df23ae7.js} +5 -5
  63. package/es/{el-message-5e6a6be9.cjs → el-message-a86c0efa.cjs} +1 -1
  64. package/es/{el-message-box-a93d2f6a.js → el-message-box-05d8cf39.js} +9 -9
  65. package/es/{el-message-box-c10adb52.cjs → el-message-box-40ff2af5.cjs} +1 -1
  66. package/es/{el-overlay-09ad71cd.js → el-overlay-cc9bc792.js} +18 -18
  67. package/es/{el-overlay-9e34965f.cjs → el-overlay-d7a6e4a9.cjs} +1 -1
  68. package/es/{el-popconfirm-70a976bf.cjs → el-popconfirm-737a015b.cjs} +1 -1
  69. package/es/{el-popconfirm-81dcd202.js → el-popconfirm-a6f66a0e.js} +4 -4
  70. package/es/{el-popper-b6c99b28.cjs → el-popper-7ba87e05.cjs} +1 -1
  71. package/es/{el-popper-b4f97157.js → el-popper-a38874f4.js} +1 -1
  72. package/es/{el-segmented-f8fce9ac.cjs → el-segmented-3fd66a0e.cjs} +1 -1
  73. package/es/{el-segmented-b868d074.js → el-segmented-51b1c797.js} +2 -2
  74. package/es/{el-select-d8d91db1.cjs → el-select-12f6deb7.cjs} +1 -1
  75. package/es/{el-select-95627997.js → el-select-1b149fab.js} +8 -8
  76. package/es/{el-table-column-376cd907.js → el-table-column-3e30ebae.js} +9 -9
  77. package/es/{el-table-column-c974cb96.cjs → el-table-column-516a0ed9.cjs} +1 -1
  78. package/es/{el-tag-66cab138.js → el-tag-0a25efdf.js} +2 -2
  79. package/es/{el-tag-a33c4b22.cjs → el-tag-789f05d3.cjs} +1 -1
  80. package/es/{el-text-c20a9f48.cjs → el-text-1470de46.cjs} +1 -1
  81. package/es/{el-text-ac60d0f2.js → el-text-73d899ff.js} +1 -1
  82. package/es/{index-d725fef6.cjs → index-2375023e.cjs} +138 -137
  83. package/es/{index-f967d6c1.cjs → index-269b22da.cjs} +1 -1
  84. package/es/{index-1f7d4f70.js → index-4194c942.js} +1 -1
  85. package/es/{index-e5566b94.js → index-42e59bf5.js} +1 -1
  86. package/es/{index-c108567d.cjs → index-4f48940d.cjs} +1 -1
  87. package/es/{index-d91dc23f.js → index-54d289d1.js} +2 -2
  88. package/es/{index-844bdd85.js → index-6806997d.js} +2 -2
  89. package/es/{index-f3562b52.cjs → index-696b6a94.cjs} +1 -1
  90. package/es/{index-88546436.js → index-6e967429.js} +2 -2
  91. package/es/{index-a871c3eb.js → index-87b4bf61.js} +75 -60
  92. package/es/{index-298075cf.cjs → index-8de94a49.cjs} +1 -1
  93. package/es/{index-57672682.js → index-94e43e0d.js} +2 -2
  94. package/es/{index-bc8e277e.js → index-ac98a4d8.js} +12827 -12573
  95. package/es/{index-11547a0c.cjs → index-c04f444f.cjs} +1 -1
  96. package/es/{index-098c2447.cjs → index-cebc7160.cjs} +1 -1
  97. package/es/{index-9b9ef5dd.cjs → index-ee977f79.cjs} +1 -1
  98. package/es/{python-ecde9ff2.js → python-a914569a.js} +39 -11
  99. package/es/{python-c27ba105.cjs → python-c67c8901.cjs} +2 -2
  100. package/es/style.css +1 -1
  101. package/es/{use-form-common-props-fd9b61a0.cjs → use-form-common-props-344056f9.cjs} +1 -1
  102. package/es/{use-form-common-props-815d48a6.js → use-form-common-props-47e50c10.js} +28 -28
  103. package/es/{use-global-config-30d7d8ce.cjs → use-global-config-cf78ebac.cjs} +1 -1
  104. package/es/{use-global-config-b5e9d3d5.js → use-global-config-f52caea0.js} +4 -4
  105. package/es/{validator-1b8a6128.cjs → validator-3cad04b2.cjs} +1 -1
  106. package/es/{validator-764a9db0.js → validator-94c04152.js} +1 -1
  107. package/es/{zh-cn-e963c628.js → zh-cn-4921961d.js} +1 -1
  108. package/es/{zh-cn-90317f62.cjs → zh-cn-aabfaa94.cjs} +1 -1
  109. package/lib/bundle.js +1 -1
  110. package/lib/bundle.umd.cjs +225 -223
  111. package/lib/{index-d976d634.js → index-0c373441.js} +29980 -29522
  112. package/lib/{python-12eba14f.js → python-4e746381.js} +1 -1
  113. package/lib/style.css +1 -1
  114. package/package.json +2 -1
  115. package/packages/MonacoEditor/index.vue +741 -70
  116. package/packages/VarietySearch/components/AddTag/index.vue +18 -6
  117. package/packages/VarietySearch/components/FactorScreen/index.vue +80 -12
  118. package/packages/VarietySearch/components/FactorScreen/tools.js +41 -0
  119. package/packages/VarietySearch/index.vue +0 -8
  120. package/src/main.ts +16 -11
  121. package/src/pages/MonacoEditor/index.vue +1 -0
@@ -1,15 +1,18 @@
1
1
  <script setup>
2
2
  import { debounce } from "st-func";
3
+ import { InfoFilled } from "@element-plus/icons-vue";
3
4
  import { ref, onMounted, onUnmounted, nextTick, inject } from "vue";
5
+ import { extractVariables } from "./tools";
4
6
  import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
5
7
  import "monaco-editor/esm/vs/basic-languages/python/python.contribution";
6
8
  import "monaco-editor/esm/vs/basic-languages/lua/lua.contribution";
7
- import { extractVariables } from "./tools";
9
+ import VarietySelect from "../VarSelectDialog/VarietySelect.vue";
8
10
 
9
11
  const { request } = inject("stConfig"); // 组件库全局配置
10
12
 
11
13
  const emit = defineEmits(["change"]);
12
14
  const props = defineProps({
15
+ // 编辑器相关参数
13
16
  defaultValue: {
14
17
  type: String,
15
18
  default: "",
@@ -27,6 +30,16 @@ const props = defineProps({
27
30
  type: Boolean,
28
31
  default: false,
29
32
  },
33
+ // 变量列表开关
34
+ variableEnable: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ // 变量列表类型
39
+ useCase: {
40
+ type: String,
41
+ default: "1", // 1-选股, 2-回测
42
+ },
30
43
  });
31
44
 
32
45
  let editorInstance = null;
@@ -39,6 +52,209 @@ const suggestionIndex = ref(-1);
39
52
  const suggestionBoxRef = ref(null);
40
53
  const isShowingSuggestions = ref(false);
41
54
 
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
+
42
258
  // [建议列表] 数据获取
43
259
  const getSuggestions = debounce(async (keyword = "") => {
44
260
  if (!props.suggestionEnable) return;
@@ -55,6 +271,7 @@ const getSuggestions = debounce(async (keyword = "") => {
55
271
  hideSuggestions();
56
272
  }
57
273
  }, 200);
274
+
58
275
  // [建议列表] 展示
59
276
  const showSuggestions = async (options) => {
60
277
  if (!props.suggestionEnable) return;
@@ -63,6 +280,7 @@ const showSuggestions = async (options) => {
63
280
  isShowingSuggestions.value = true;
64
281
  updateSuggestionBoxPosition();
65
282
  };
283
+
66
284
  // [建议列表] 隐藏
67
285
  const hideSuggestions = () => {
68
286
  if (!props.suggestionEnable) return;
@@ -70,6 +288,7 @@ const hideSuggestions = () => {
70
288
  suggestionIndex.value = -1;
71
289
  isShowingSuggestions.value = false;
72
290
  };
291
+
73
292
  // [建议列表] 更新DOM位置
74
293
  const updateSuggestionBoxPosition = () => {
75
294
  if (!props.suggestionEnable) return;
@@ -82,8 +301,7 @@ const updateSuggestionBoxPosition = () => {
82
301
  const editorDom = editorInstance.getDomNode();
83
302
  const editorRect = editorDom.getBoundingClientRect();
84
303
 
85
- // 计算相对于编辑器容器的位置
86
- const left = cursorCoords.left + editorRect.left + 40; // 在光标右侧
304
+ const left = cursorCoords.left + editorRect.left + 40;
87
305
  const top = cursorCoords.top + editorRect.top + cursorCoords.height;
88
306
 
89
307
  suggestionBoxRef.value.style.left = `${left}px`;
@@ -91,6 +309,7 @@ const updateSuggestionBoxPosition = () => {
91
309
  }
92
310
  });
93
311
  };
312
+
94
313
  // [建议列表] 插入内容到编辑器
95
314
  const insertSelectedSuggestion = () => {
96
315
  if (!props.suggestionEnable) return;
@@ -102,7 +321,6 @@ const insertSelectedSuggestion = () => {
102
321
  if (varArray.length) {
103
322
  const str = varArray.reduce((result, item, index, arry) => {
104
323
  result += `${item}=`;
105
- // 如果不是最后一个, 就需要加个逗号
106
324
  if (index < arry.length - 1) result += ",";
107
325
  return result;
108
326
  }, "");
@@ -148,9 +366,49 @@ const insertSelectedSuggestion = () => {
148
366
 
149
367
  hideSuggestions();
150
368
  };
151
- // [建议列表] 随用户内容输入变动检查建议项
152
- const checkForSuggestions = async () => {
369
+
370
+ // [建议列表] 选项自动滚动
371
+ const scrollToSelectedSuggestion = () => {
153
372
  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 () => {
154
412
  const position = editorInstance.getPosition();
155
413
  const model = editorInstance.getModel();
156
414
  const lineContent = model.getLineContent(position.lineNumber);
@@ -158,104 +416,140 @@ const checkForSuggestions = async () => {
158
416
  const textBeforeCursor = lineContent.substring(0, position.column - 1);
159
417
  const lastHashIndex = textBeforeCursor.lastIndexOf("#");
160
418
 
419
+ // 如果是#触发,显示建议列表
161
420
  if (lastHashIndex !== -1) {
421
+ // 如果变量列表正在显示,先隐藏
422
+ if (isShowingVariables.value) hideVariables();
423
+
162
424
  const keyword = textBeforeCursor.substring(lastHashIndex + 1);
163
425
  if (lastCursorPosition && (lastCursorPosition.lineNumber !== position.lineNumber || lastCursorPosition.column !== position.column)) {
164
426
  hideSuggestions();
165
427
  }
166
428
  lastCursorPosition = { ...position };
167
429
 
168
- getSuggestions(keyword);
169
- } else {
170
- hideSuggestions();
430
+ if (props.suggestionEnable) {
431
+ getSuggestions(keyword);
432
+ }
171
433
  }
172
- };
173
- // [建议列表] 选项自动滚动
174
- const scrollToSelectedSuggestion = () => {
175
- if (!props.suggestionEnable) return;
176
- if (!suggestionBoxRef.value || suggestionIndex.value === -1) return;
434
+ // 否则检查变量列表
435
+ else {
436
+ // 如果建议列表正在显示,先隐藏
437
+ if (isShowingSuggestions.value) hideSuggestions();
177
438
 
178
- const suggestionList = suggestionBoxRef.value.querySelector(".suggestion-list");
179
- const selectedItem = suggestionBoxRef.value.querySelector(".suggestion-item.selected");
180
-
181
- if (suggestionList && selectedItem) {
182
- const listRect = suggestionList.getBoundingClientRect();
183
- const itemRect = selectedItem.getBoundingClientRect();
439
+ // 获取光标前的单词作为关键字
440
+ const wordUntilPosition = model.getWordUntilPosition(position);
441
+ const keyword = wordUntilPosition.word;
184
442
 
185
- // 如果选中项在可视区域上方
186
- if (itemRect.top < listRect.top) {
187
- suggestionList.scrollTop -= listRect.top - itemRect.top;
443
+ if (lastCursorPosition && (lastCursorPosition.lineNumber !== position.lineNumber || lastCursorPosition.column !== position.column)) {
444
+ hideVariables();
188
445
  }
189
- // 如果选中项在可视区域下方
190
- else if (itemRect.bottom > listRect.bottom) {
191
- suggestionList.scrollTop += itemRect.bottom - listRect.bottom;
446
+ lastCursorPosition = { ...position };
447
+
448
+ if (props.variableEnable && keyword.length > 0) {
449
+ getVariables(keyword);
450
+ } else if (keyword.length === 0) {
451
+ hideVariables();
192
452
  }
193
453
  }
194
454
  };
195
455
 
196
456
  // [代码编辑器] 键盘事件
197
457
  const handleKeyDown = (e) => {
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;
206
- }
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;
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;
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;
229
485
  }
230
- case "Escape": {
231
- e.preventDefault();
232
- e.stopPropagation();
233
- hideSuggestions();
234
- break;
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;
235
522
  }
236
- default:
237
- break;
238
523
  }
239
524
  };
525
+
240
526
  // [代码编辑器] 内容变化回调
241
527
  const handleContentChange = (event) => {
242
528
  if (!editorInstance) return;
243
529
  emit("change", editorInstance.getValue());
244
- checkForSuggestions();
530
+ checkForCompletions();
245
531
  };
532
+
246
533
  // [代码编辑器] 光标变化回调
247
534
  const handleCursorPositionChange = (event) => {
248
535
  if (isShowingSuggestions.value) {
249
536
  updateSuggestionBoxPosition();
250
537
  }
538
+ if (isShowingVariables.value) {
539
+ updateVariableBoxPosition();
540
+ }
251
541
  };
252
542
 
253
543
  // [document] 全局监听的点击事件
254
544
  const handleClickOutside = (event) => {
255
- // 如果点击了建议列表外面的区域, 就自动关闭
545
+ // 如果点击了建议列表外面的区域,就自动关闭
256
546
  if (suggestionBoxRef.value && !suggestionBoxRef.value.contains(event.target)) {
257
547
  hideSuggestions();
258
548
  }
549
+ // 如果点击了变量列表外面的区域,就自动关闭
550
+ if (variableBoxRef.value && !variableBoxRef.value.contains(event.target)) {
551
+ hideVariables();
552
+ }
259
553
  };
260
554
 
261
555
  onMounted(() => {
@@ -264,15 +558,22 @@ onMounted(() => {
264
558
  language: props.language,
265
559
  theme: props.theme,
266
560
  });
561
+
267
562
  editorInstance.onKeyDown(handleKeyDown);
268
563
  editorInstance.onDidChangeModelContent(handleContentChange);
269
564
  editorInstance.onDidChangeCursorPosition(handleCursorPositionChange);
565
+
270
566
  document.addEventListener("click", handleClickOutside);
567
+
568
+ // 获取变量列表数据
569
+ fetchVariables();
271
570
  });
571
+
272
572
  onUnmounted(() => {
273
573
  editorInstance?.dispose();
274
574
  document.removeEventListener("click", handleClickOutside);
275
575
  });
576
+
276
577
  defineExpose({
277
578
  resize: () => {
278
579
  editorInstance.layout();
@@ -295,11 +596,9 @@ defineExpose({
295
596
  const model = editorInstance.getModel();
296
597
  if (!model) return;
297
598
 
298
- // 获取最后一行
299
599
  const lineCount = model.getLineCount();
300
600
  const lastLine = model.getLineContent(lineCount);
301
601
 
302
- // 设置光标位置到最后一行末尾
303
602
  const position = {
304
603
  lineNumber: lineCount,
305
604
  column: lastLine.length + 1,
@@ -307,8 +606,6 @@ defineExpose({
307
606
 
308
607
  editorInstance.setPosition(position);
309
608
  editorInstance.focus();
310
-
311
- // 滚动到光标位置
312
609
  editorInstance.revealPositionInCenter(position);
313
610
  },
314
611
  });
@@ -321,6 +618,7 @@ defineExpose({
321
618
  ref="editorContainer"
322
619
  class="editor-container"
323
620
  />
621
+
324
622
  <!-- 建议列表 -->
325
623
  <Teleport
326
624
  to="body"
@@ -350,9 +648,157 @@ defineExpose({
350
648
  <div class="suggestion-footer">使用 ↑↓ 选择,Enter 确认,Esc 取消</div>
351
649
  </div>
352
650
  </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>
353
780
  </div>
354
781
  </template>
355
782
 
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>
356
802
  <style scoped lang="scss">
357
803
  .editor-wrapper {
358
804
  position: relative;
@@ -363,6 +809,8 @@ defineExpose({
363
809
  width: 100%;
364
810
  height: 100%;
365
811
  }
812
+
813
+ // 建议列表样式
366
814
  .suggestion-box {
367
815
  position: fixed;
368
816
  background: #1e1e1e;
@@ -387,6 +835,7 @@ defineExpose({
387
835
  transform: rotate(45deg);
388
836
  z-index: -1;
389
837
  }
838
+
390
839
  .suggestion-header {
391
840
  display: flex;
392
841
  justify-content: space-between;
@@ -406,6 +855,7 @@ defineExpose({
406
855
  opacity: 0.8;
407
856
  }
408
857
  }
858
+
409
859
  .suggestion-list {
410
860
  max-height: 200px;
411
861
  overflow-y: auto;
@@ -458,6 +908,7 @@ defineExpose({
458
908
  }
459
909
  }
460
910
  }
911
+
461
912
  .suggestion-footer {
462
913
  padding: 6px 12px;
463
914
  background: #2d2d30;
@@ -467,4 +918,224 @@ defineExpose({
467
918
  text-align: center;
468
919
  }
469
920
  }
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
+ }
470
1141
  </style>