st-comp 0.0.209 → 0.0.211

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "st-comp",
3
3
  "public": true,
4
- "version": "0.0.209",
4
+ "version": "0.0.211",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -1,6 +1,6 @@
1
1
  <!-- 常用指标组件 -->
2
2
  <script setup name="CommonIndicator">
3
- import { ref, watch, computed } from "vue";
3
+ import { ref, watch, computed, inject } from "vue";
4
4
 
5
5
  const perVolumnRadioOptions = [
6
6
  { label: "近2周", value: "1" },
@@ -16,6 +16,7 @@ const auditOpinionTypeRadioOptions = [
16
16
  { label: "无法表示意见", value: 4 },
17
17
  ];
18
18
 
19
+ const clearRow = inject("clearRow");
19
20
  const data = defineModel("data", { default: [] });
20
21
  const props = defineProps({
21
22
  config: { type: Object, default: () => {} },
@@ -369,7 +370,7 @@ const changeMainFlowRadioType = () => {
369
370
  <div class="indicator">
370
371
  <div class="title">
371
372
  <span>常用指标: </span>
372
- <span>不限</span>
373
+ <span @click="clearRow('commonIndicator')">不限</span>
373
374
  </div>
374
375
  <div class="options">
375
376
  <span
@@ -381,7 +382,7 @@ const changeMainFlowRadioType = () => {
381
382
  </span>
382
383
  </div>
383
384
  </div>
384
- <!-- 指标Tag -->
385
+ <!-- TagList: 已选指标 -->
385
386
  <div class="tags">
386
387
  <el-tag
387
388
  v-for="(item, index) in data"
@@ -397,8 +398,7 @@ const changeMainFlowRadioType = () => {
397
398
  >
398
399
  </el-tag>
399
400
  </div>
400
-
401
- <!-- 指标弹窗 -->
401
+ <!-- TagInfo: 指标具体弹窗 -->
402
402
  <el-dialog
403
403
  v-model="visible"
404
404
  :title="nowIndicator.label"
@@ -716,7 +716,6 @@ const changeMainFlowRadioType = () => {
716
716
  .common-indicator {
717
717
  .indicator {
718
718
  display: flex;
719
- align-items: start;
720
719
  .title {
721
720
  height: 24px;
722
721
  min-width: 100px;
@@ -733,13 +732,15 @@ const changeMainFlowRadioType = () => {
733
732
  }
734
733
  }
735
734
  .options {
735
+ display: flex;
736
+ flex-wrap: wrap;
736
737
  span {
737
738
  font-size: 12px;
739
+ height: 24;
738
740
  line-height: 24px;
739
741
  color: var(--el-text-color-regular);
740
- padding: 4px 0;
741
- margin-right: 20px;
742
742
  cursor: pointer;
743
+ margin-right: 16px;
743
744
  &:hover {
744
745
  color: var(--el-color-primary);
745
746
  }
@@ -0,0 +1,243 @@
1
+ <script setup>
2
+ import { Close, Plus } from "@element-plus/icons-vue";
3
+ import { ref, watch, computed, inject } from "vue";
4
+
5
+ const clearRow = inject("clearRow");
6
+
7
+ const data = defineModel("data", { default: [] });
8
+ const props = defineProps({
9
+ config: { type: Object, default: () => {} },
10
+ varietyMarket: { type: null || Number, default: () => null }, // 已选品种市场
11
+ commonOption: { type: Array, default: () => [] }, // 已选常用选项
12
+ });
13
+ const visible = ref(false);
14
+ const compositeOrderForm = ref({ name: null, value: "desc" });
15
+
16
+ // 组合排序数据源 [受到: 品种市场, 常用选项影响]
17
+ const compositeOrderOptions = computed(() => {
18
+ let result = [];
19
+ // 如果品种市场和常用选项一个都没有选, 那么就展示全部的常用指标
20
+ if (props.varietyMarket === null && !props.commonOption.length) {
21
+ result = props.config.options;
22
+ } else {
23
+ result = props.config.options;
24
+ // 1. 如果选择了品种市场, 进行一波筛选
25
+ if (props.varietyMarket) {
26
+ result = result.filter(({ parent }) => parent.varietyMarketIds.includes(props.varietyMarket));
27
+ }
28
+ // 2. 如果选择了常用选项, 进行一波筛选
29
+ if (props.commonOption.length) {
30
+ result = result.filter(({ parent }) => {
31
+ const { commonOptionIds } = parent;
32
+ // 只要选择了的常用选项里面, 有commonOptionIds中的值, 那么就展示, 即判断两个数组是否有重复元素
33
+ return [...new Set([...commonOptionIds, ...props.commonOption])].length !== [...commonOptionIds, ...props.commonOption].length;
34
+ });
35
+ }
36
+ }
37
+ return result;
38
+ });
39
+ // 条件: 新增, 编辑, 窗口确认, 删除
40
+ const handleAction = (action, index) => {
41
+ switch (action) {
42
+ // 新增
43
+ case "add": {
44
+ compositeOrderForm.value = { name: null, value: "desc" };
45
+ visible.value = true;
46
+ break;
47
+ }
48
+ // 编辑
49
+ case "edit": {
50
+ const item = data.value[index];
51
+ compositeOrderForm.value = { ...item };
52
+ visible.value = true;
53
+ break;
54
+ }
55
+ // 窗口确认
56
+ case "submit": {
57
+ if (!compositeOrderForm.value.name) return ElMessage.warning("请选择需要排序的条件");
58
+ const { label } = compositeOrderOptions.value.find((item) => item.key === compositeOrderForm.value.name);
59
+ const tagText = `${label}-${compositeOrderForm.value.value === "asc" ? "正序↑" : "降序↓"}`;
60
+ // 判断data中是否已存在同字段条件, 是则替换, 否则push
61
+ const keyIndex = data.value.findIndex(({ name }) => name === compositeOrderForm.value.name);
62
+ if (keyIndex === -1) {
63
+ data.value.push({ ...compositeOrderForm.value, tagText });
64
+ } else {
65
+ data.value.splice(keyIndex, 1, { ...compositeOrderForm.value, tagText });
66
+ }
67
+ visible.value = false;
68
+ break;
69
+ }
70
+ // 删除
71
+ case "delete": {
72
+ data.value.splice(index, 1);
73
+ break;
74
+ }
75
+ }
76
+ };
77
+
78
+ // 监视排序数据源变动, 已勾选且新的数据源中也有它,则要进行勾选保留
79
+ watch(
80
+ () => compositeOrderOptions.value,
81
+ () => {
82
+ if (data.value.length) {
83
+ data.value = data.value.filter(({ name }) => {
84
+ return compositeOrderOptions.value.find((item) => item.key === name);
85
+ });
86
+ }
87
+ }
88
+ );
89
+ </script>
90
+
91
+ <template>
92
+ <div
93
+ class="composite-order"
94
+ v-if="config.show && compositeOrderOptions.length"
95
+ >
96
+ <div class="title">
97
+ <span>组合排序: </span>
98
+ <span @click="clearRow('compositeOrder')">不限</span>
99
+ </div>
100
+ <div class="tag-box">
101
+ <el-tag
102
+ v-for="(item, index) in data"
103
+ closable
104
+ type="info"
105
+ @close="handleAction('delete', index)"
106
+ >
107
+ <span>{{ item.tagText }}</span>
108
+ <span
109
+ class="edit"
110
+ @click="handleAction('edit', index)"
111
+ >编辑</span
112
+ >
113
+ </el-tag>
114
+ <el-button
115
+ type="primary"
116
+ plain
117
+ size="small"
118
+ :icon="Plus"
119
+ @click="handleAction('add')"
120
+ >
121
+ 添加排序
122
+ </el-button>
123
+ </div>
124
+ <el-dialog
125
+ modal-class="order-dialog"
126
+ v-model="visible"
127
+ width="400"
128
+ align-center
129
+ destroy-on-close
130
+ draggable
131
+ overflow
132
+ :modal="false"
133
+ :modal-penetrable="true"
134
+ :show-close="false"
135
+ >
136
+ <template #header="{ titleId, titleClass }">
137
+ <div class="custom-header">
138
+ <div class="left">
139
+ <span
140
+ :id="titleId"
141
+ :class="titleClass"
142
+ >
143
+ 添加条件
144
+ </span>
145
+ </div>
146
+ <div class="right">
147
+ <el-icon @click="visible = false"><Close /></el-icon>
148
+ </div>
149
+ </div>
150
+ </template>
151
+ <el-form
152
+ ref="compositeOrderFormRef"
153
+ :model="compositeOrderForm"
154
+ >
155
+ <el-form-item label="添加条件: ">
156
+ <el-select
157
+ v-model="compositeOrderForm.name"
158
+ filterable
159
+ placeholder="请选择"
160
+ no-match-text="未匹配到相关条件"
161
+ >
162
+ <el-option
163
+ v-for="{ label, key } in compositeOrderOptions"
164
+ :label="label"
165
+ :value="key"
166
+ :key="key"
167
+ />
168
+ </el-select>
169
+ </el-form-item>
170
+ <el-form-item label="排序方式: ">
171
+ <el-radio-group v-model="compositeOrderForm.value">
172
+ <el-radio
173
+ label="正序"
174
+ value="asc"
175
+ />
176
+ <el-radio
177
+ label="倒序"
178
+ value="desc"
179
+ />
180
+ </el-radio-group>
181
+ </el-form-item>
182
+ </el-form>
183
+ <template #footer>
184
+ <div class="dialog-footer">
185
+ <el-button @click="visible = false">取消</el-button>
186
+ <el-button
187
+ type="primary"
188
+ @click="handleAction('submit')"
189
+ >
190
+ 确定
191
+ </el-button>
192
+ </div>
193
+ </template>
194
+ </el-dialog>
195
+ </div>
196
+ </template>
197
+
198
+ <style lang="scss" scoped>
199
+ .composite-order {
200
+ display: flex;
201
+ .title {
202
+ height: 24px;
203
+ min-width: 100px;
204
+ font-size: 12px;
205
+ line-height: 24px;
206
+ display: flex;
207
+ align-items: center;
208
+ span:nth-child(1) {
209
+ width: 60px;
210
+ }
211
+ span:nth-child(2) {
212
+ cursor: pointer;
213
+ color: var(--el-color-primary);
214
+ }
215
+ }
216
+ .tag-box {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 10px;
220
+ .edit {
221
+ cursor: pointer;
222
+ color: var(--el-color-primary);
223
+ margin-left: 6px;
224
+ }
225
+ }
226
+ .order-dialog {
227
+ .custom-header {
228
+ display: flex;
229
+ align-items: center;
230
+ justify-content: space-between;
231
+ .left,
232
+ .right {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 10px;
236
+ }
237
+ .el-icon {
238
+ cursor: pointer;
239
+ }
240
+ }
241
+ }
242
+ }
243
+ </style>
@@ -1,11 +1,13 @@
1
1
  <!-- 因子筛选组件 -->
2
2
  <script setup name="FactorScreen">
3
- import { nextTick, ref, watch } from "vue";
4
- import { Close, Plus, CircleCloseFilled, InfoFilled } from "@element-plus/icons-vue";
3
+ import { nextTick, ref, watch, inject, reactive } from "vue";
4
+ import { Close, Plus, CircleCloseFilled, InfoFilled, Document } from "@element-plus/icons-vue";
5
5
  import { handleVerifyScore, extractConditionDetails, extractVariables } from "./tools.js";
6
6
  import FactorDescription from "./FactorDescription.vue";
7
7
  import MonacoEditor from "../../../MonacoEditor/index.vue";
8
8
 
9
+ const { request } = inject("stConfig"); // 组件库全局配置
10
+
9
11
  const props = defineProps({
10
12
  config: {
11
13
  type: Object,
@@ -29,10 +31,20 @@ const data = defineModel("data", {
29
31
 
30
32
  const monacoEditorRef = ref();
31
33
  const stVarSelectDialogRef = ref();
34
+
32
35
  // 弹窗开关
33
36
  const visible = ref(false);
34
37
  const visibleDescriptions = ref(false);
35
38
  const factorType = ref("脚本");
39
+ const scriptTestLoading = ref(false);
40
+ const scriptCopyLoading = ref(false);
41
+ const scriptTestLogVisible = ref(false);
42
+ const scriptTestResult = reactive({
43
+ result: null,
44
+ detail: "",
45
+ code: "",
46
+ });
47
+
36
48
  // 弹窗表单
37
49
  const dialogFormRef = ref(null);
38
50
  const dialogForm = ref({
@@ -227,7 +239,7 @@ const handleDeleteTag = (aciton, index) => {
227
239
  const open = () => {
228
240
  stVarSelectDialogRef.value.open(monacoEditorRef.value);
229
241
  };
230
- // 插入自定义函数
242
+ // 脚本: 插入自定义函数
231
243
  const handleInsertCustomFunction = (funcName, funcExpression) => {
232
244
  if (!monacoEditorRef.value) return ElMessage.error("未检测到编辑器实例");
233
245
  // 生成基础插入内容 (如果内含${}, 需提取出来, 作为变量)
@@ -288,6 +300,54 @@ const handleInsertCustomFunction = (funcName, funcExpression) => {
288
300
  editorIns.focus();
289
301
  }
290
302
  };
303
+ // 脚本: 复制
304
+ const handleScriptCopy = async () => {
305
+ try {
306
+ scriptCopyLoading.value = true;
307
+ const script = monacoEditorRef.value.getValue();
308
+ if (!script) return ElMessage.error("请输入脚本语句");
309
+ const { body } = await request.post("/common/qt/getFuncExpr", { factorSelectExpr: script });
310
+ if (!body) return ElMessage.error("脚本解析失败, 请检查脚本内容是否填写完整或者联系管理员");
311
+ // 复制内容到粘贴板
312
+ let txa = document.createElement("textarea");
313
+ txa.value = body;
314
+ document.body.appendChild(txa);
315
+ txa.select();
316
+ document.execCommand("copy");
317
+ document.body.removeChild(txa);
318
+ ElMessage.success("脚本内容已经成功复制到粘贴板");
319
+ } finally {
320
+ scriptCopyLoading.value = false;
321
+ }
322
+ };
323
+ // 脚本: 测试
324
+ const handleScriptTest = async () => {
325
+ try {
326
+ scriptTestLoading.value = true;
327
+ const script = monacoEditorRef.value.getValue();
328
+ if (!script) return ElMessage.error("请输入脚本语句");
329
+ const { body } = await request.post("/common/qt/getFuncExpr", { factorSelectExpr: script });
330
+ if (!body) return ElMessage.error("脚本解析失败, 请检查脚本内容是否填写完整或者联系管理员");
331
+ const testRes = await request.post("/common/qt/testFactorSelect", { factorSelectExpr: body });
332
+ const { result, detail } = testRes.body;
333
+ Object.assign(scriptTestResult, { result, detail, code: body });
334
+ if (result === 1) {
335
+ ElMessage.success("测试通过");
336
+ } else {
337
+ ElMessage.error("测试未能通过");
338
+ scriptTestLogVisible.value = true;
339
+ }
340
+ } finally {
341
+ scriptTestLoading.value = false;
342
+ }
343
+ };
344
+ // 脚本: 日志明细
345
+ const handleScriptLog = () => {
346
+ if (scriptTestResult.result === null) {
347
+ return ElMessage.warning("请先进行测试, 等待测试完成后可查看日志");
348
+ }
349
+ scriptTestLogVisible.value = true;
350
+ };
291
351
 
292
352
  // 监控: 窗口开关
293
353
  watch(
@@ -308,16 +368,21 @@ watch(
308
368
  factorType.value = "模版";
309
369
  }
310
370
  });
371
+ // 重置测试结果
372
+ Object.assign(scriptTestResult, {
373
+ result: null,
374
+ detail: "",
375
+ });
311
376
  break;
312
377
  }
313
378
  case false: {
314
379
  stVarSelectDialogRef.value.close();
380
+ scriptTestLogVisible.value = false;
315
381
  break;
316
382
  }
317
383
  }
318
- }
384
+ },
319
385
  );
320
-
321
386
  // 监控: 因子类型
322
387
  watch(
323
388
  () => factorType.value,
@@ -329,7 +394,7 @@ watch(
329
394
  });
330
395
  }
331
396
  },
332
- { deep: true }
397
+ { deep: true },
333
398
  );
334
399
  </script>
335
400
 
@@ -418,6 +483,7 @@ watch(
418
483
  >
419
484
  因子筛选
420
485
  </span>
486
+ <!-- 因子使用说明 -->
421
487
  <el-tooltip
422
488
  effect="dark"
423
489
  content="点击查看: 因子使用说明"
@@ -425,6 +491,7 @@ watch(
425
491
  >
426
492
  <el-icon @click="visibleDescriptions = true"><InfoFilled /></el-icon>
427
493
  </el-tooltip>
494
+ <!-- 因子模式 -->
428
495
  <el-radio-group
429
496
  v-model="factorType"
430
497
  size="small"
@@ -438,11 +505,37 @@ watch(
438
505
  value="脚本"
439
506
  />
440
507
  </el-radio-group>
441
- <st-customFunction
508
+ <!-- 脚本模式: 自定义函数插入新建, 脚本测试, 脚本复制 -->
509
+ <div
510
+ class="editor-tools"
442
511
  v-show="factorType === '脚本'"
443
- size="small"
444
- @insert="handleInsertCustomFunction"
445
- />
512
+ >
513
+ <st-customFunction
514
+ v-show="factorType === '脚本'"
515
+ size="small"
516
+ @insert="handleInsertCustomFunction"
517
+ />
518
+ <el-button
519
+ type="primary"
520
+ size="small"
521
+ :loading="scriptCopyLoading"
522
+ @click="handleScriptCopy"
523
+ >复制</el-button
524
+ >
525
+ <el-button
526
+ type="primary"
527
+ size="small"
528
+ :loading="scriptTestLoading"
529
+ @click="handleScriptTest"
530
+ >测试</el-button
531
+ >
532
+ <el-button
533
+ size="small"
534
+ :icon="Document"
535
+ @click="handleScriptLog"
536
+ >日志明细</el-button
537
+ >
538
+ </div>
446
539
  </div>
447
540
  <!-- 变量选择器 + 关闭 -->
448
541
  <div class="right">
@@ -719,6 +812,57 @@ watch(
719
812
  :factorType="factorType"
720
813
  :data="config.factorDescriptions?.filter((item) => [1, 3].includes(item.type))"
721
814
  />
815
+ <!-- 窗口: 日志明细 -->
816
+ <el-dialog
817
+ modal-class="log-dialog"
818
+ v-model="scriptTestLogVisible"
819
+ width="830"
820
+ align-center
821
+ append-to-body
822
+ draggable
823
+ overflow
824
+ :modal="false"
825
+ :modal-penetrable="true"
826
+ :show-close="false"
827
+ >
828
+ <template #header="{ titleId, titleClass }">
829
+ <div class="custom-header">
830
+ <div class="left">
831
+ <span
832
+ :id="titleId"
833
+ :class="titleClass"
834
+ >
835
+ 日志明细
836
+ </span>
837
+ </div>
838
+ <!-- 关闭 -->
839
+ <div class="right">
840
+ <el-icon @click="scriptTestLogVisible = false"><Close /></el-icon>
841
+ </div>
842
+ </div>
843
+ </template>
844
+ <div class="content">
845
+ <!-- 代码 -->
846
+ <el-scrollbar
847
+ class="code"
848
+ height="600px"
849
+ >
850
+ <pre>{{ scriptTestResult.code }}</pre>
851
+ </el-scrollbar>
852
+ <!-- 分割线 -->
853
+ <el-divider direction="vertical" />
854
+ <!-- 日志 -->
855
+ <el-scrollbar height="600px">
856
+ <pre :class="scriptTestResult.result === 1 ? 'success-log' : 'error-log'">{{ scriptTestResult.result === 1 ? "测试通过 √" : scriptTestResult.detail }}</pre>
857
+ </el-scrollbar>
858
+ </div>
859
+ <!-- 底部 -->
860
+ <template #footer>
861
+ <div class="dialog-footer">
862
+ <el-button @click="scriptTestLogVisible = false"> 关闭 </el-button>
863
+ </div>
864
+ </template>
865
+ </el-dialog>
722
866
  <!-- 变量选择器 -->
723
867
  <st-varSelectDialog ref="stVarSelectDialogRef" />
724
868
  </template>
@@ -738,6 +882,14 @@ watch(
738
882
  align-items: center;
739
883
  gap: 10px;
740
884
  }
885
+ .editor-tools {
886
+ display: flex;
887
+ align-items: center;
888
+ gap: 10px;
889
+ .el-button {
890
+ margin: 0;
891
+ }
892
+ }
741
893
  .el-icon {
742
894
  cursor: pointer;
743
895
  }
@@ -760,4 +912,49 @@ watch(
760
912
  }
761
913
  }
762
914
  }
915
+ .log-dialog {
916
+ .custom-header {
917
+ display: flex;
918
+ align-items: center;
919
+ justify-content: space-between;
920
+ .left,
921
+ .right {
922
+ display: flex;
923
+ align-items: center;
924
+ gap: 10px;
925
+ }
926
+ .el-icon {
927
+ cursor: pointer;
928
+ }
929
+ }
930
+ .content {
931
+ display: flex;
932
+ background-color: var(--el-color-black);
933
+ .el-scrollbar {
934
+ flex: 1;
935
+ box-sizing: border-box;
936
+ padding: 8px;
937
+ }
938
+ .el-divider {
939
+ margin: 0;
940
+ height: 616px;
941
+ }
942
+ .code {
943
+ color: var(--el-color-info);
944
+ }
945
+ .success-log {
946
+ color: var(--el-color-success);
947
+ }
948
+ .error-log {
949
+ color: var(--el-color-danger);
950
+ }
951
+ pre {
952
+ margin: 0;
953
+ line-height: 1.5;
954
+ white-space: pre-wrap;
955
+ word-break: break-all;
956
+ word-wrap: break-word;
957
+ }
958
+ }
959
+ }
763
960
  </style>
@@ -1,6 +1,6 @@
1
1
  // 品种池搜索组件的默认配置项
2
- export default {
3
- // 品种市场 [选项: 预警字典1000]
2
+ const config = {
3
+ // 品种市场 [选项: 接口]
4
4
  varietyMarket: {
5
5
  show: true,
6
6
  options: [
@@ -13,7 +13,7 @@ export default {
13
13
  // { label: "期权", value: 8, memo: [11] },
14
14
  ],
15
15
  },
16
- // 常用选项 [选项: 预警字典1003]
16
+ // 常用选项 [选项: 接口]
17
17
  commonOption: {
18
18
  show: true,
19
19
  options: [
@@ -33,7 +33,7 @@ export default {
33
33
  // { label: "概念板块指数", value: 15 },
34
34
  ],
35
35
  },
36
- // 上市板块 [选项: 预警字典1030]
36
+ // 上市板块 [选项: 接口]
37
37
  marketIds: {
38
38
  show: true,
39
39
  options: [
@@ -47,7 +47,7 @@ export default {
47
47
  // { label: "深港通", value: 8192, varietyMarketIds: "5" },
48
48
  ],
49
49
  },
50
- // 自定标签 [选项: 接口获取]
50
+ // 自定标签 [选项: 接口]
51
51
  customTag: {
52
52
  show: true,
53
53
  options: [],
@@ -64,7 +64,7 @@ export default {
64
64
  { label: "中证1000", value: "ZZ1000CFJC" },
65
65
  ],
66
66
  },
67
- // 因子筛选 [选项: 接口获取]
67
+ // 因子筛选 [选项: 接口]
68
68
  factorScreen: {
69
69
  show: true,
70
70
  // SQL功能是否展示
@@ -77,7 +77,7 @@ export default {
77
77
  factorOptions: [], // 因子下拉框数据源
78
78
  factorDescriptions: [], // 因子使用说明数据源
79
79
  },
80
- // 常用指标 [选项: 组件默认]
80
+ // 常用指标 [选项: 前端默认]
81
81
  commonIndicator: {
82
82
  show: true,
83
83
  options: [
@@ -615,3 +615,12 @@ export default {
615
615
  ],
616
616
  },
617
617
  };
618
+
619
+ export default {
620
+ ...config,
621
+ // 组合排序 [选项: 前端默认] 本质就是给常用指标排序用的, 用的就是常用指标的数据源
622
+ compositeOrder: {
623
+ show: true,
624
+ options: config.commonIndicator.options,
625
+ },
626
+ };