vue2-client 1.19.76 → 1.19.78

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/docs/index.md CHANGED
@@ -1,4 +1,11 @@
1
1
  ## 文档列表
2
+ ### 业务逻辑与请求
3
+ * [前端业务逻辑使用说明](前端业务逻辑使用说明.md) - 用与后端同语法的表达式在前端操作界面组件的逻辑脚本(LogicRunner / runExpression)
4
+ * [函数使用相关](函数使用相关.md) - 配置中 function、this.runLogic、getComponentByName、openDialog 等
5
+ * [Logic函数使用相关](Logic函数使用相关.md) - 后端 Logic 插件(log、sql、entity 等)
6
+ * [请求工具使用说明](请求工具使用说明.md) - post/usePost/useRunLogic、去重、Loading
7
+ * [组件注册表插件使用说明](组件注册表插件使用说明.md) - 按名称注册/获取当前界面组件($componentRegistry)
8
+
2
9
  ### common
3
10
  * [CreateQuery 完整查询配置生成](../src/base-client/components/common/CreateQuery/index.md)
4
11
  * [CreateSimpleFormQuery 基础表单配置生成](../src/base-client/components/common/CreateSimpleFormQuery/index.md)
@@ -0,0 +1,139 @@
1
+ # 前端业务逻辑使用说明
2
+
3
+ 前端业务逻辑指:**用与后端相同的表达式语法书写**,在**浏览器中执行**,用于**操作界面组件与响应式数据**的逻辑脚本。语法与后端 Logic 一致,但运行在前端,入参中可传入 Vue 实例、ref、响应式数据等,供脚本读写界面状态或调用组件方法。
4
+
5
+ ---
6
+
7
+ ## 一、运行方式
8
+
9
+ ### 1. 按配置名执行:`LogicRunner.run(logicName, param)`
10
+
11
+ - 从后端/配置中心按 `logicName` 拉取逻辑内容(与后端 Logic 同源的配置),在前端执行。
12
+ - 执行时自动注入插件:`data`(即本次传入的 `param`)、`log`、`ENV`、`logic`、`dateTools`、`vueTools`。
13
+ - 适合逻辑内容放在配置里、由界面传入「当前组件 / 响应式数据」的场景。
14
+
15
+ ```javascript
16
+ import LogicRunner from '@vue2-client/logic/LogicRunner'
17
+
18
+ // 传入当前组件 proxy 和响应式数据,供逻辑脚本操作界面
19
+ const result = await LogicRunner.run('testVueLogic', {
20
+ metaData: metaData, // 例如 ref({ inputValue, displayValue })
21
+ proxy: proxy // 当前组件实例 getCurrentInstance().proxy
22
+ })
23
+ ```
24
+
25
+ ### 2. 直接执行表达式字符串:`LogicRunner.runExpression(source, params)`
26
+
27
+ - 不拉取配置,直接执行一段表达式字符串。
28
+ - `params` 即脚本内的入参(通常包含 `data` 等),无默认插件,需调用方自己构造。
29
+ - 适合逻辑内容来自表单配置、工作流配置等动态字符串的场景。
30
+
31
+ ```javascript
32
+ import LogicRunner from '@vue2-client/logic/LogicRunner'
33
+
34
+ const result = await LogicRunner.runExpression(expression, {
35
+ WF_FORM: { ...formData } // 脚本内通过 data.WF_FORM 或对应 key 访问
36
+ })
37
+ ```
38
+
39
+ ---
40
+
41
+ ## 二、前端可用插件 / 入参
42
+
43
+ 在**按配置名执行**(`LogicRunner.run`)时,脚本内可用的对象包括:
44
+
45
+ | 名称 | 说明 |
46
+ |------|------|
47
+ | `data` | 本次调用传入的 `param`,例如 `data.metaData`、`data.proxy`。用于读写在页面传入的响应式数据或组件引用。 |
48
+ | `log` | 日志,如 `log.info(...)`、`log.debug(...)`。 |
49
+ | `ENV` | 运行环境信息(来自配置的 `$globalProp`)。 |
50
+ | `logic` | LogicRunner 自身,可再调 `logic.run(其他逻辑名, 参数)`(会再拉配置执行)。 |
51
+ | `dateTools` | 日期工具,与后端 dateTools 用法一致。 |
52
+ | `vueTools` | 前端专用:`vueTools.getComponent(vueRef, componentName)`,等价于 `vueRef.$refs[componentName]`,用于按 ref 名取子组件。 |
53
+
54
+ 在**直接执行表达式**(`runExpression`)时,没有默认插件,完全由第二个参数 `params` 提供,例如把当前表单数据放进 `params.WF_FORM`,脚本里用 `data.WF_FORM` 访问(若 params 里 key 为 `data` 且值为该对象)。
55
+
56
+ ---
57
+
58
+ ## 三、语法要点(与后端 Logic 一致)
59
+
60
+ - 整体是一条**表达式**,多条语句用逗号 `,` 分隔。
61
+ - **赋值**:`变量 = 表达式` 或 `对象.属性 = 表达式`。
62
+ - **条件**:`条件 : 结果 , 默认值`,无 if/else,用逗号接默认值。
63
+ - **循环**:用数组的 `each`,例如 `data.xxx.each( row = row + 1 )`,其中 `row` 表示当前项。
64
+ - **JSON**:支持 `{ key: value }`、`[ a, b ]`。
65
+ - **字符串插值**:字符串内用 `"现在是{dateTools.getNow2()}"` 形式嵌入表达式。
66
+ - 其他:`return`、`try/catch/finally`、`validate { ... }`、`assert`、lambda `=> { }` 等,与后端一致。
67
+
68
+ 更完整的语法见 `src/expression/core/Program.js`(解析)和 `src/expression/TestExpression.js`(大量表达式示例)。
69
+
70
+ ---
71
+
72
+ ## 四、操作界面组件的示例
73
+
74
+ ### 示例 1:在页面中执行“前端逻辑”并操作组件与数据
75
+
76
+ 页面中传入 `proxy` 和响应式数据 `metaData`,逻辑脚本内通过 `data` 修改界面数据,或通过 `vueTools` 取子组件调用方法:
77
+
78
+ ```vue
79
+ <!-- src/pages/LogicCallExample/index.vue -->
80
+ <script setup>
81
+ import LogicRunner from '@vue2-client/logic/LogicRunner'
82
+ import { getCurrentInstance, ref } from 'vue'
83
+
84
+ const { proxy } = getCurrentInstance()
85
+ const metaData = ref({
86
+ inputValue: '',
87
+ displayValue: 'default'
88
+ })
89
+
90
+ async function testLogic () {
91
+ await LogicRunner.run('testVueLogic', {
92
+ metaData: metaData,
93
+ proxy: proxy
94
+ })
95
+ }
96
+ </script>
97
+
98
+ <template>
99
+ <div>
100
+ 录入值:<a-input v-model="metaData.inputValue" />
101
+ 显示值:<span>{{ metaData.displayValue }}</span>
102
+ <a-button @click="testLogic">修改</a-button>
103
+ <x-form-table ref="testFormTable" ... />
104
+ </div>
105
+ </template>
106
+ ```
107
+
108
+ 配置项 `testVueLogic` 中的逻辑脚本可以这样写(与后端同语法,操作 `data` 与组件):
109
+
110
+ - 改界面上的响应式数据:`data.metaData.displayValue = data.metaData.inputValue , null`
111
+ - 通过 `vueTools` 拿子组件:`vueTools.getComponent(data.proxy, 'testFormTable')`,再调用该组件的 refreshTable 等方法(需在表达式语法内通过属性/方法调用完成)。
112
+
113
+ ### 示例 2:工作流中根据表单数据做分支判断
114
+
115
+ 不涉及组件引用,仅根据表单数据执行表达式,得到 true/false 或结果值:
116
+
117
+ ```javascript
118
+ // 摘自 WorkFlowHandle.vue:根据表单数据评估条件表达式
119
+ const result = await LogicRunner.runExpression(expression, {
120
+ WF_FORM: { ...formData }
121
+ })
122
+ // 表达式内可用 data.WF_FORM.xxx 访问表单字段
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 五、相关文件与文档
128
+
129
+ | 内容 | 路径 |
130
+ |------|------|
131
+ | 运行器 | `src/logic/LogicRunner.js` |
132
+ | 表达式解析与执行 | `src/expression/ExpressionRunner.js`、`src/expression/core/Program.js`、`src/expression/core/Expression.js` |
133
+ | 前端插件 | `src/logic/plugins/index.js`、`src/logic/plugins/common/VueTools.js`、`src/logic/plugins/common/DateTools.js` |
134
+ | 页面示例 | `src/pages/LogicCallExample/index.vue` |
135
+ | 工作流表达式示例 | `src/pages/WorkflowDetail/WorkflowPageDetail/WorkFlowHandle.vue`(`evaluateExpression`) |
136
+ | 表达式语法用例 | `src/expression/TestExpression.js` |
137
+ | 后端 Logic 插件说明 | `docs/Logic函数使用相关.md`(后端 sql、entity、log 等;前端 run 时仅有上述插件) |
138
+
139
+ 当前项目**没有单独一份“前端逻辑语法手册”**,语法与后端业务逻辑一致,可参考后端说明与 `Program.js`、`TestExpression.js` 对照使用。本文档侧重前端运行方式、入参/插件和操作界面组件的用法。
@@ -0,0 +1,64 @@
1
+ # 组件注册表插件(ComponentRegistryPlugin)使用说明
2
+
3
+ 当前界面组件管理插件,用于按名称注册、获取组件实例。同名重复注册会报错,组件销毁时自动从注册表移除。
4
+
5
+ ## API
6
+
7
+ | 方法 | 说明 |
8
+ |------|------|
9
+ | `register(name, component)` | 注册组件。`name` 唯一,重复则抛错。组件销毁时自动注销。 |
10
+ | `get(name)` | 按名称获取组件实例,未注册返回 `undefined`。 |
11
+ | `has(name)` | 是否已注册该名称。 |
12
+ | `unregister(name)` | 从注册表移除(不销毁组件)。 |
13
+ | `clear()` | 清空所有注册(整页切换等场景慎用)。 |
14
+
15
+ ## 使用方式
16
+
17
+ 在组件或根实例上通过 `this.$componentRegistry` 访问(已随 base-client Plugins 全局注册)。
18
+
19
+ **注册**(在需要被按名获取的组件里,如 `mounted`):
20
+
21
+ ```js
22
+ mounted() {
23
+ this.$componentRegistry.register('main', this)
24
+ }
25
+ ```
26
+
27
+ **获取**:
28
+
29
+ ```js
30
+ const main = this.$componentRegistry.get('main')
31
+ if (main && main.refreshTable) main.refreshTable()
32
+ ```
33
+
34
+ **判断**:
35
+
36
+ ```js
37
+ if (this.$componentRegistry.has('sidebar')) { ... }
38
+ ```
39
+
40
+ ## 注意
41
+
42
+ - 名称会做 `trim()`,空字符串注册会报错。
43
+ - 重复注册同名会抛出:`[ComponentRegistry] 组件名称 "xxx" 已存在,不可重复注册`。
44
+ - 注册表为应用级单例,跨页面共享;整页卸载时如有多页签/单页内多视图,可按需在适当时机调用 `clear()`。
45
+
46
+ ## 与 Report-Grid(XReportGrid)配合
47
+
48
+ 栅格报表的插槽单元格配置中,除插槽名 `slotRef` 外,可**可选**增加字段 **`globalRegistryName`**。仅当配置了 `globalRegistryName` 时,该插槽对应的组件才会注册到全局组件注册表;未配置时行为与旧版一致,仅注册到报表内部的 `getComponentByName`,**保证向后兼容**。
49
+
50
+ 示例(插槽配置):
51
+
52
+ ```json
53
+ {
54
+ "slotRef": "main",
55
+ "globalRegistryName": "reportMainFormTable",
56
+ "slotType": "x-form-table",
57
+ "slotConfig": "crud_xxx"
58
+ }
59
+ ```
60
+
61
+ - `slotRef`:插槽名,用于报表内 `getComponentByName('main')` 等。
62
+ - `globalRegistryName`:全局注册名,配置后可通过 `this.$componentRegistry.get('reportMainFormTable')` 在任意处获取;报表销毁时会自动从全局注册表注销。
63
+
64
+ 源文件:`src/base-client/plugins/ComponentRegistryPlugin.js`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-client",
3
- "version": "1.19.76",
3
+ "version": "1.19.78",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
@@ -26,7 +26,8 @@ const wrapperClassObject = computed(() => {
26
26
  'expanded-grid-white',
27
27
  'report-mode',
28
28
  // listView模式下隐藏“已选择”按钮
29
- 'listview-hide-selected'
29
+ 'listview-hide-selected',
30
+ 'min-height-auto'
30
31
  ]
31
32
  for (const key of booleanStyleKeys) {
32
33
  const val = a[key]
@@ -280,6 +281,19 @@ const computedDefaultPageSize = computed(() => {
280
281
  }
281
282
  }
282
283
  }
284
+ // 表格最小高度自动
285
+ &.h-form-table-min-height-auto {
286
+ :deep(.table-wrapper) {
287
+ .ant-table:not(.ant-table-empty) {
288
+ .ant-table-content {
289
+ min-height: auto !important;
290
+ .ant-table-body {
291
+ min-height: auto !important;
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
283
297
 
284
298
  // 修复数据量少时下拉框被挡住的问题:为表格内容区域设置最小高度
285
299
  // 当表格有数据时,确保表格内容区域有足够高度,避免下拉框被挡住
@@ -3,7 +3,15 @@
3
3
  <x-form-table
4
4
  title="示例表单"
5
5
  :queryParamsName="queryParamsName"
6
- serviceName="af-linepatrol"
6
+ :fixedAddForm="fixedAddForm"
7
+ :externalSelectedRowKeys="selectedKeys"
8
+ @action="action"
9
+ @selectRow="selectRow"
10
+ @columnClick="columnClick"
11
+ @ceshi="ceshi"
12
+ @rowDblClick="rowDblClick"
13
+ :defaultPageSize="5"
14
+ serviceName="af-revenue"
7
15
  ref="xFormTable"
8
16
  ></x-form-table>
9
17
  </a-card>
@@ -20,7 +28,7 @@ export default {
20
28
  data() {
21
29
  return {
22
30
  // 查询配置文件名
23
- queryParamsName: 't_subhighpressure_pipelineCRUD',
31
+ queryParamsName: 'address_management',
24
32
  // 查询配置左侧tree
25
33
  xTreeConfigName: 'addressType',
26
34
  // 新增表单固定值
@@ -1,186 +1,186 @@
1
- // print.js
2
-
3
- export function printElement (elementToPrint) {
4
- // 创建一个新的浏览器窗口
5
- const printWindow = window.open('', '_blank', 'height=1024,width=768')
6
- // 设置新窗口的文档内容
7
- printWindow.document.write(`
8
- <html>
9
- <head>
10
- <title>Print</title>
11
- <style>
12
- @page {
13
- size: auto;
14
- margin: 0mm;
15
- }
16
- html, body {
17
- margin: 0;
18
- padding: 0;
19
- width: 100%;
20
- height: 100%;
21
- }
22
- #print-container {
23
- display: none
24
- }
25
- .img{
26
- width: 95%;
27
- height: 180px;
28
- object-fit: cover;
29
- }
30
- .reportMain {
31
- text-align: center;
32
- margin: 0 auto;
33
- font-size: 16px;
34
- color: #000;
35
- background-color: #fff;
36
- padding: 15px;
37
- border-radius: 8px;
38
-
39
- .reportTitle {
40
- font-weight: bold;
41
- }
42
-
43
- .subTitle {
44
- display: flex;
45
- justify-content: space-between;
46
- margin-bottom: 1%;
47
-
48
- .subTitleItems {
49
- max-width: 30%;
50
- }
51
- }
52
-
53
- .inputsDiv {
54
- display: flex;
55
- justify-content: space-between;
56
- .inputsDivItem {
57
- display: flex;
58
- align-items: center;
59
- padding: 0 4px;
60
- white-space: nowrap;
61
- .inputsDivItemLabel {
62
- padding: 0 4px;
63
- }
64
- }
65
- }
66
-
67
- .reportTable {
68
- width: 100%;
69
- border-collapse: collapse;
70
- table-layout:fixed;
71
- word-break:break-all;
72
- text-align: center;
73
- }
74
- }
75
- .reportMainForDisplay {
76
- text-align: center;
77
- margin: 10% auto;
78
- font-size: 16px;
79
- color: #000;
80
- background-color: #fff;
81
- padding: 15px;
82
- border-radius: 8px;
83
-
84
- .reportTitle {
85
- font-weight: bold;
86
- }
87
-
88
- .subTitle {
89
- display: flex;
90
- justify-content: space-between;
91
-
92
- .subTitleItems {
93
- max-width: 30%;
94
- }
95
- }
96
-
97
- .inputsDiv {
98
- display: flex;
99
- justify-content: space-around;
100
- .inputsDivItem {
101
- display: flex;
102
- align-items: center;
103
- padding: 0 4px;
104
- white-space: nowrap;
105
- .inputsDivItemLabel {
106
- padding: 0 4px;
107
- }
108
- }
109
- }
110
-
111
- .reportTable {
112
- width: 100%;
113
- border-collapse: collapse;
114
- table-layout:fixed;
115
- word-break:break-all;
116
- }
117
- }
118
- .reportMainNoPadding {
119
- text-align: center;
120
- margin: 0 auto;
121
- font-size: 16px;
122
- color: #000;
123
- background-color: #fff;
124
- border-radius: 8px;
125
-
126
- .reportTitle {
127
- font-weight: bold;
128
- }
129
-
130
- .subTitle {
131
- display: flex;
132
- justify-content: space-between;
133
-
134
- .subTitleItems {
135
- max-width: 30%;
136
- }
137
- }
138
-
139
- .inputsDiv {
140
- display: flex;
141
- justify-content: space-between;
142
- .inputsDivItem {
143
- display: flex;
144
- align-items: center;
145
- padding: 0 4px;
146
- white-space: nowrap;
147
- .inputsDivItemLabel {
148
- padding: 0 4px;
149
- }
150
- }
151
- }
152
-
153
- .reportTable {
154
- width: 100%;
155
- border-collapse: collapse;
156
- table-layout:fixed;
157
- word-break:break-all;
158
- }
159
- }
160
- .tools{
161
- position: fixed;
162
- right: 2%;
163
- text-align: right;
164
- width: 60%;
165
- cursor: pointer;
166
- .toolsItem{
167
- width: 15%;
168
- margin-right: 3%;
169
- display: inline-block;
170
- }
171
- }
172
- </style>
173
- </head>
174
- <body>
175
- <!-- 将需要打印的元素内容复制到新窗口中 -->
176
- ${elementToPrint.innerHTML}
177
- </body>
178
- </html>
179
- `)
180
- // 延迟执行打印,以确保新窗口的内容已加载完成
181
- printWindow.document.close() // 关闭文档流,确保内容完全加载
182
- setTimeout(() => {
183
- printWindow.print() // 调用打印方法
184
- printWindow.close()
185
- }, 500) // 延迟500毫秒后执行打印
186
- }
1
+ // print.js
2
+
3
+ export function printElement (elementToPrint) {
4
+ // 创建一个新的浏览器窗口
5
+ const printWindow = window.open('', '_blank', 'height=1024,width=768')
6
+ // 设置新窗口的文档内容
7
+ printWindow.document.write(`
8
+ <html>
9
+ <head>
10
+ <title>Print</title>
11
+ <style>
12
+ @page {
13
+ size: auto;
14
+ margin: 0mm;
15
+ }
16
+ html, body {
17
+ margin: 0;
18
+ padding: 0;
19
+ width: 100%;
20
+ height: 100%;
21
+ }
22
+ #print-container {
23
+ display: none
24
+ }
25
+ .img{
26
+ width: 95%;
27
+ height: 180px;
28
+ object-fit: cover;
29
+ }
30
+ .reportMain {
31
+ text-align: center;
32
+ margin: 0 auto;
33
+ font-size: 16px;
34
+ color: #000;
35
+ background-color: #fff;
36
+ padding: 15px;
37
+ border-radius: 8px;
38
+
39
+ .reportTitle {
40
+ font-weight: bold;
41
+ }
42
+
43
+ .subTitle {
44
+ display: flex;
45
+ justify-content: space-between;
46
+ margin-bottom: 1%;
47
+
48
+ .subTitleItems {
49
+ max-width: 30%;
50
+ }
51
+ }
52
+
53
+ .inputsDiv {
54
+ display: flex;
55
+ justify-content: space-between;
56
+ .inputsDivItem {
57
+ display: flex;
58
+ align-items: center;
59
+ padding: 0 4px;
60
+ white-space: nowrap;
61
+ .inputsDivItemLabel {
62
+ padding: 0 4px;
63
+ }
64
+ }
65
+ }
66
+
67
+ .reportTable {
68
+ width: 100%;
69
+ border-collapse: collapse;
70
+ table-layout:fixed;
71
+ word-break:break-all;
72
+ text-align: center;
73
+ }
74
+ }
75
+ .reportMainForDisplay {
76
+ text-align: center;
77
+ margin: 10% auto;
78
+ font-size: 16px;
79
+ color: #000;
80
+ background-color: #fff;
81
+ padding: 15px;
82
+ border-radius: 8px;
83
+
84
+ .reportTitle {
85
+ font-weight: bold;
86
+ }
87
+
88
+ .subTitle {
89
+ display: flex;
90
+ justify-content: space-between;
91
+
92
+ .subTitleItems {
93
+ max-width: 30%;
94
+ }
95
+ }
96
+
97
+ .inputsDiv {
98
+ display: flex;
99
+ justify-content: space-around;
100
+ .inputsDivItem {
101
+ display: flex;
102
+ align-items: center;
103
+ padding: 0 4px;
104
+ white-space: nowrap;
105
+ .inputsDivItemLabel {
106
+ padding: 0 4px;
107
+ }
108
+ }
109
+ }
110
+
111
+ .reportTable {
112
+ width: 100%;
113
+ border-collapse: collapse;
114
+ table-layout:fixed;
115
+ word-break:break-all;
116
+ }
117
+ }
118
+ .reportMainNoPadding {
119
+ text-align: center;
120
+ margin: 0 auto;
121
+ font-size: 16px;
122
+ color: #000;
123
+ background-color: #fff;
124
+ border-radius: 8px;
125
+
126
+ .reportTitle {
127
+ font-weight: bold;
128
+ }
129
+
130
+ .subTitle {
131
+ display: flex;
132
+ justify-content: space-between;
133
+
134
+ .subTitleItems {
135
+ max-width: 30%;
136
+ }
137
+ }
138
+
139
+ .inputsDiv {
140
+ display: flex;
141
+ justify-content: space-between;
142
+ .inputsDivItem {
143
+ display: flex;
144
+ align-items: center;
145
+ padding: 0 4px;
146
+ white-space: nowrap;
147
+ .inputsDivItemLabel {
148
+ padding: 0 4px;
149
+ }
150
+ }
151
+ }
152
+
153
+ .reportTable {
154
+ width: 100%;
155
+ border-collapse: collapse;
156
+ table-layout:fixed;
157
+ word-break:break-all;
158
+ }
159
+ }
160
+ .tools{
161
+ position: fixed;
162
+ right: 2%;
163
+ text-align: right;
164
+ width: 60%;
165
+ cursor: pointer;
166
+ .toolsItem{
167
+ width: 15%;
168
+ margin-right: 3%;
169
+ display: inline-block;
170
+ }
171
+ }
172
+ </style>
173
+ </head>
174
+ <body>
175
+ <!-- 将需要打印的元素内容复制到新窗口中 -->
176
+ ${elementToPrint.innerHTML}
177
+ </body>
178
+ </html>
179
+ `)
180
+ // 延迟执行打印,以确保新窗口的内容已加载完成
181
+ printWindow.document.close() // 关闭文档流,确保内容完全加载
182
+ setTimeout(() => {
183
+ printWindow.print() // 调用打印方法
184
+ printWindow.close()
185
+ }, 500) // 延迟500毫秒后执行打印
186
+ }
@@ -211,10 +211,18 @@ export default {
211
211
  configEnded: false,
212
212
  // 是否已收到 Design 的 allSlotsConfigEnded(所有 slot 子组件配置结束)
213
213
  allSlotsConfigEnded: false,
214
+ // 本报表注册到全局组件注册表的名称(仅当配置了 globalRegistryName 时才有),destroy 时统一注销
215
+ _globalRegistrySlotNames: [],
214
216
  }
215
217
  },
216
218
  beforeDestroy () {
217
219
  clearInterval(this.timer)
220
+ if (this.$componentRegistry && this._globalRegistrySlotNames.length) {
221
+ this._globalRegistrySlotNames.forEach((name) => {
222
+ this.$componentRegistry.unregister(name)
223
+ })
224
+ this._globalRegistrySlotNames = []
225
+ }
218
226
  },
219
227
  watch: {
220
228
  // 如果配置名更改了,重新获取配置
@@ -337,9 +345,18 @@ export default {
337
345
  this.allSlotsConfigEnded = true
338
346
  this._markConfigEnded()
339
347
  },
340
- // 把组件注册到refs中,方便调用
341
- registerComponent (componentName, component) {
348
+ // 把组件注册到refs中,方便调用;若传入 options.globalRegistryName 则同时注册到全局组件注册表(向后兼容:无该字段则不注册)
349
+ registerComponent (componentName, component, options = {}) {
342
350
  this.$refs[componentName] = component
351
+ const globalName = options.globalRegistryName
352
+ if (globalName && this.$componentRegistry) {
353
+ try {
354
+ this.$componentRegistry.register(globalName, component)
355
+ this._globalRegistrySlotNames.push(globalName)
356
+ } catch (e) {
357
+ this.$logger('report.registerComponent').warn('全局组件注册表注册失败:', e?.message || e)
358
+ }
359
+ }
343
360
  },
344
361
 
345
362
  // 把设计的table布局转换成可显示的珊格布局
@@ -589,9 +606,18 @@ export default {
589
606
  this.table_selectedRows = selectedRows
590
607
  this.$emit('selectRow', selectedRowKeys, selectedRows)
591
608
  },
592
- // 注册组件到$refs
593
- registerComponentToRefs (componentName, component) {
609
+ // 注册组件到$refs中;若传入 options.globalRegistryName 则同时注册到全局组件注册表(向后兼容:无该字段则不注册)
610
+ registerComponentToRefs (componentName, component, options = {}) {
594
611
  this.$refs[componentName] = component
612
+ const globalName = options.globalRegistryName
613
+ if (globalName && this.$componentRegistry) {
614
+ try {
615
+ this.$componentRegistry.register(globalName, component)
616
+ this._globalRegistrySlotNames.push(globalName)
617
+ } catch (e) {
618
+ this.$logger('report.registerComponentToRefs').warn('全局组件注册表注册失败:', e?.message || e)
619
+ }
620
+ }
595
621
  },
596
622
 
597
623
  // 提交处理,调用配置中的函数
@@ -423,7 +423,7 @@ export default {
423
423
  this.mixinData = this.getMixinData()
424
424
  }
425
425
  if (cell.slotRef && this.registerComponent) {
426
- this.registerComponent(cell.slotRef, this.$refs[`dynamicComponent_${cell.slotRef || cellIndex}`][0])
426
+ this.registerComponent(cell.slotRef, this.$refs[`dynamicComponent_${cell.slotRef || cellIndex}`][0], { globalRegistryName: cell.globalRegistryName })
427
427
  }
428
428
  // 传递给祖先组件
429
429
  const shouldInit = cell.shouldInit == null ? true : cell.shouldInit
@@ -593,16 +593,24 @@ export default {
593
593
  },
594
594
  // 把用户定义的组件,传递到整个杉格中,方便调用
595
595
  passComponentNamesToAncestor (refs) {
596
- // 遍历所有 refs
596
+ const findCellBySlotRef = (cols, slotRef) => {
597
+ if (!Array.isArray(cols)) return cols && cols.slotRef === slotRef ? cols : null
598
+ for (const c of cols) {
599
+ if (c && !Array.isArray(c) && c.slotRef === slotRef) return c
600
+ if (Array.isArray(c)) {
601
+ const found = findCellBySlotRef(c, slotRef)
602
+ if (found) return found
603
+ }
604
+ }
605
+ return null
606
+ }
597
607
  Object.entries(refs).forEach(([refKey, refValue]) => {
598
- // 检查 ref 是否以 dynamicComponent_ 开头
599
608
  if (refKey.startsWith('dynamicComponent_')) {
600
609
  const componentRef = refValue[0]
601
610
  if (componentRef && this.registerComponent) {
602
- // 去掉前缀并获取组件名字
603
- const index = refKey.replace('dynamicComponent_', '') // 去掉前缀
604
- // 传递给祖先组件(由根 XReport 注入并绑定)
605
- this.registerComponent(index, componentRef)
611
+ const index = refKey.replace('dynamicComponent_', '')
612
+ const cell = findCellBySlotRef(this.columns, index)
613
+ this.registerComponent(index, componentRef, { globalRegistryName: cell?.globalRegistryName })
606
614
  }
607
615
  }
608
616
  })
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 当前界面组件管理插件
3
+ * - 支持按名称注册组件实例、按名称获取组件
4
+ * - 同名重复注册时抛错
5
+ * - 组件销毁时自动从注册表移除
6
+ *
7
+ * 使用方式:
8
+ * - 注册:this.$componentRegistry.register('main', this)
9
+ * - 获取:this.$componentRegistry.get('main')
10
+ * - 判断:this.$componentRegistry.has('main')
11
+ * - 注销:this.$componentRegistry.unregister('main')
12
+ */
13
+
14
+ const REGISTRY = new Map()
15
+
16
+ function createRegistry () {
17
+ return {
18
+ /**
19
+ * 注册组件
20
+ * @param {string} name 组件名称(唯一)
21
+ * @param {import('vue').Vue} component 组件实例(Vue 实例)
22
+ * @throws {Error} 当 name 已存在时
23
+ */
24
+ register (name, component) {
25
+ if (name == null || typeof name !== 'string' || !name.trim()) {
26
+ throw new Error('[ComponentRegistry] 组件名称不能为空')
27
+ }
28
+ const key = name.trim()
29
+ if (REGISTRY.has(key)) {
30
+ throw new Error(`[ComponentRegistry] 组件名称 "${key}" 已存在,不可重复注册`)
31
+ }
32
+ REGISTRY.set(key, component)
33
+ const self = this
34
+ component.$once('hook:beforeDestroy', () => {
35
+ self.unregister(key)
36
+ })
37
+ },
38
+
39
+ /**
40
+ * 根据名称获取已注册的组件
41
+ * @param {string} name 组件名称
42
+ * @returns {import('vue').Vue | undefined}
43
+ */
44
+ get (name) {
45
+ if (name == null || typeof name !== 'string') return undefined
46
+ return REGISTRY.get(name.trim())
47
+ },
48
+
49
+ /**
50
+ * 判断名称是否已注册
51
+ * @param {string} name 组件名称
52
+ * @returns {boolean}
53
+ */
54
+ has (name) {
55
+ if (name == null || typeof name !== 'string') return false
56
+ return REGISTRY.has(name.trim())
57
+ },
58
+
59
+ /**
60
+ * 注销指定名称的组件(仅从注册表移除,不销毁组件)
61
+ * @param {string} name 组件名称
62
+ */
63
+ unregister (name) {
64
+ if (name == null || typeof name !== 'string') return
65
+ REGISTRY.delete(name.trim())
66
+ },
67
+
68
+ /**
69
+ * 清空当前所有注册(用于整页切换等场景,慎用)
70
+ */
71
+ clear () {
72
+ REGISTRY.clear()
73
+ }
74
+ }
75
+ }
76
+
77
+ const registryInstance = createRegistry()
78
+
79
+ const ComponentRegistryPlugin = {
80
+ install (Vue) {
81
+ Vue.prototype.$componentRegistry = registryInstance
82
+ Vue.$componentRegistry = registryInstance
83
+ }
84
+ }
85
+
86
+ export default ComponentRegistryPlugin
@@ -3,6 +3,7 @@ import GetAppDataService from '@vue2-client/base-client/plugins/AppData'
3
3
  import getConfig from '@vue2-client/base-client/plugins/Config'
4
4
  import moment from '@vue2-client/base-client/plugins/moment'
5
5
  import recording from '@vue2-client/base-client/plugins/Recording'
6
+ import ComponentRegistryPlugin from '@vue2-client/base-client/plugins/ComponentRegistryPlugin'
6
7
 
7
8
  import VueI18nPlugin from './i18n-extend'
8
9
  import AuthorityPlugin from './authority-plugin'
@@ -18,6 +19,7 @@ const Plugins = {
18
19
  Vue.use(AuthorityPlugin)
19
20
  Vue.use(TabsPagePlugin)
20
21
  Vue.use(recording)
22
+ Vue.use(ComponentRegistryPlugin)
21
23
  }
22
24
  }
23
25
  export default Plugins
@@ -42,7 +42,7 @@
42
42
  :local-config="tabDesigner"
43
43
  :body-style="{ padding: 0 }"
44
44
  :tabBarGutter="24"
45
- :extra-data="{ workflowId: workflowId }"
45
+ :extra-data="{ workflowId: workflowId, stepId: activeStepId }"
46
46
  default-active-key="workFlowTab">
47
47
  <a-tab-pane
48
48
  :forceRender="true"
@@ -70,7 +70,7 @@
70
70
  v-if="showTab"
71
71
  :compProp="{ buttonState: { extra: true }, disableAction: false }"
72
72
  :local-config="tabDesigner"
73
- :extra-data="{ workflowId:workflowId }"
73
+ :extra-data="{ workflowId: workflowId, stepId: activeStepId }"
74
74
  :body-style="{ padding: 0 }"
75
75
  :tabBarGutter="24"
76
76
  default-active-key="workFlowTab">
@@ -95,7 +95,7 @@ routerResource.XReportView = () => import('@vue2-client/pages/XReportView')
95
95
 
96
96
  routerResource.XReportGrid = () => import('@vue2-client/base-client/components/common/XReportGrid/XReportDemo')
97
97
 
98
- routerResource.XTab = () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue')
98
+ routerResource.XTab = () => import('@vue2-client/base-client/components/common/AfMap/demo.vue')
99
99
  routerResource.addressManage = () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue')
100
100
  // 大屏界面, 放行登录
101
101
  routerResource.bigDashboard = () => import('@vue2-client/pages/dashboard/index.vue')
@@ -1,47 +1,47 @@
1
- import AMapLoader from '@amap/amap-jsapi-loader'
2
- let Amap
3
- async function GetGDMap (secretKey, key) {
4
- if (!Amap) {
5
- window._AMapSecurityConfig = {
6
- securityJsCode: secretKey
7
- }
8
- // 解决高德地图加载报错 ---> 禁止多种API加载方式混用
9
- AMapLoader.reset()
10
- Amap = await AMapLoader.load({
11
- key: key, // 申请好的Web端开发者Key,首次调用 load 时必填
12
- version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
13
- plugins: ['AMap.IndexCluster', 'AMP.MarkerCluster', 'AMap.InfoWindow', 'AMap.HeatMap', 'AMap.HawkEye', 'AMap.DistrictSearch',
14
- 'AMap.ToolBar', 'AMap.Geolocation', 'AMap.MouseTool',
15
- 'AMap.Geocoder', 'AMap.MarkerClusterer', 'AMap.AutoComplete', 'AMap.Scale'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
16
- AMapUI: {
17
- version: '1.1', // AMapUI 缺省 1.1
18
- plugins: ['misc/PositionPicker'] // 需要加载的 AMapUI ui插件
19
- }
20
- })
21
- }
22
- return Amap
23
- }
24
-
25
- async function getGDMap (address) {
26
- new (await GetGDMap()).Geocoder({
27
- radius: 500 // 范围,默认:500
28
- }).getLocation(address, function (status, result) {
29
- if (status === 'complete' && result.geocodes.length) {
30
- return ({ lng: result.geocodes[0].location.lng, lat: result.geocodes[0].location.lat })
31
- } else {
32
- // eslint-disable-next-line prefer-promise-reject-errors
33
- throw new Error('根据经纬度查询地址失败')
34
- }
35
- })
36
- }
37
-
38
- async function GetLocation (address) {
39
- return new Promise((resolve, reject) => {
40
- try {
41
- resolve(getGDMap(address))
42
- } catch (e) {
43
- reject(e)
44
- }
45
- })
46
- }
47
- export { GetGDMap, GetLocation }
1
+ import AMapLoader from '@amap/amap-jsapi-loader'
2
+ let Amap
3
+ async function GetGDMap (secretKey, key) {
4
+ if (!Amap) {
5
+ window._AMapSecurityConfig = {
6
+ securityJsCode: secretKey
7
+ }
8
+ // 解决高德地图加载报错 ---> 禁止多种API加载方式混用
9
+ AMapLoader.reset()
10
+ Amap = await AMapLoader.load({
11
+ key: key, // 申请好的Web端开发者Key,首次调用 load 时必填
12
+ version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
13
+ plugins: ['AMap.IndexCluster', 'AMP.MarkerCluster', 'AMap.InfoWindow', 'AMap.HeatMap', 'AMap.HawkEye', 'AMap.DistrictSearch',
14
+ 'AMap.ToolBar', 'AMap.Geolocation', 'AMap.MouseTool',
15
+ 'AMap.Geocoder', 'AMap.MarkerClusterer', 'AMap.AutoComplete', 'AMap.Scale'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
16
+ AMapUI: {
17
+ version: '1.1', // AMapUI 缺省 1.1
18
+ plugins: ['misc/PositionPicker'] // 需要加载的 AMapUI ui插件
19
+ }
20
+ })
21
+ }
22
+ return Amap
23
+ }
24
+
25
+ async function getGDMap (address) {
26
+ new (await GetGDMap()).Geocoder({
27
+ radius: 500 // 范围,默认:500
28
+ }).getLocation(address, function (status, result) {
29
+ if (status === 'complete' && result.geocodes.length) {
30
+ return ({ lng: result.geocodes[0].location.lng, lat: result.geocodes[0].location.lat })
31
+ } else {
32
+ // eslint-disable-next-line prefer-promise-reject-errors
33
+ throw new Error('根据经纬度查询地址失败')
34
+ }
35
+ })
36
+ }
37
+
38
+ async function GetLocation (address) {
39
+ return new Promise((resolve, reject) => {
40
+ try {
41
+ resolve(getGDMap(address))
42
+ } catch (e) {
43
+ reject(e)
44
+ }
45
+ })
46
+ }
47
+ export { GetGDMap, GetLocation }