cloud-web-corejs 1.1.0-dev.53 → 1.1.0-dev.55
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 +3 -1
- package/src/app-bootstrap.sync.js +502 -0
- package/src/components/xform/form-designer/designer.js +1 -0
- package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +30 -4
- package/src/components/xform/form-designer/form-widget/dialog/searchFormDialog.vue +27 -4
- package/src/components/xform/form-designer/form-widget/field-widget/input-widget.vue +28 -2
- package/src/components/xform/form-designer/setting-panel/form-setting.vue +28 -7
- package/src/components/xform/form-designer/setting-panel/property-editor/suffixIconClickable-editor.vue +23 -0
- package/src/components/xform/form-designer/setting-panel/propertyRegister.js +1 -0
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +1 -0
- package/src/components/xform/form-render/indexMixin.js +17 -2
- package/src/components/xform/form-render/pageDialogScope.js +54 -0
- package/src/components/xform/lang/en-US.js +1 -0
- package/src/components/xform/lang/zh-CN.js +1 -0
- package/src/components/xform/utils/util.js +1 -0
- package/src/directive/el-dialog-center/index.js +3 -2
- package/src/directive/el-drag-dialog/drag.js +33 -4
- package/src/index.sync.js +5 -0
- package/src/router/modules/customer.js +1 -4
- package/src/styles/page-shell.scss +91 -0
- package/src/views/user/form/view/list.vue +24 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloud-web-corejs",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.1.0-dev.
|
|
4
|
+
"version": "1.1.0-dev.55",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build:ace-resources": "node build/build-ace-resources.js",
|
|
7
7
|
"build:tinymce-resources": "node build/build-tinymce-resources.js",
|
|
@@ -212,9 +212,11 @@
|
|
|
212
212
|
"src/permission.js",
|
|
213
213
|
"src/index.js",
|
|
214
214
|
"src/index.next.js",
|
|
215
|
+
"src/index.sync.js",
|
|
215
216
|
"src/boot-assets.legacy.js",
|
|
216
217
|
"src/boot-assets.next.js",
|
|
217
218
|
"src/app-bootstrap.js",
|
|
219
|
+
"src/app-bootstrap.sync.js",
|
|
218
220
|
"src/public-path.js",
|
|
219
221
|
"src/microRouter",
|
|
220
222
|
"src/App.vue"
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
// 同步启动:与 app-bootstrap.js 相同,但 excelImport / VabUpload / jsonImport / langImport 不走 lazyPrototypePlugin。
|
|
2
|
+
// 由 index.sync.js 接入。
|
|
3
|
+
import Vue from "vue";
|
|
4
|
+
import Cookies from "js-cookie";
|
|
5
|
+
import Element from "element-ui";
|
|
6
|
+
import App from "@base/App";
|
|
7
|
+
import store from "@base/store";
|
|
8
|
+
import router, { resetRouter } from "@base/router";
|
|
9
|
+
import settings from "@/settings";
|
|
10
|
+
import { setToken as setAuthToken } from "@base/utils/auth";
|
|
11
|
+
import { loadViewComponentAsync } from "@base/utils/resolveViewComponent";
|
|
12
|
+
import { installRuntimeRecovery } from "@base/utils/runtimeRecovery";
|
|
13
|
+
|
|
14
|
+
import "@base/permission"; // permission control
|
|
15
|
+
|
|
16
|
+
import * as filters from "@base/filters"; // global filters
|
|
17
|
+
|
|
18
|
+
// main.js 文件添加以下代码
|
|
19
|
+
|
|
20
|
+
import BaseKeepAlive from "@base/utils/keepAlive.js";
|
|
21
|
+
|
|
22
|
+
Vue.component("BaseKeepAlive", BaseKeepAlive);
|
|
23
|
+
|
|
24
|
+
import VXETable from "vxe-table";
|
|
25
|
+
import "vxe-table/lib/style.css";
|
|
26
|
+
import "@base/components/table/style.scss";
|
|
27
|
+
import "@base/components/fileLibrary/style.scss";
|
|
28
|
+
import "@base/components/VabUpload/style.scss";
|
|
29
|
+
import "@base/components/xform/styles/table-layout.scss";
|
|
30
|
+
import "@base/components/xform/styles/designer.scss";
|
|
31
|
+
import "@base/components/xform/styles/census.scss";
|
|
32
|
+
import "@base/components/xform/styles/titles.scss";
|
|
33
|
+
import "@base/components/xform/styles/containers.scss";
|
|
34
|
+
import "@base/components/xform/styles/skins.scss";
|
|
35
|
+
import "@base/components/xform/styles/detail-temp.scss";
|
|
36
|
+
import "@base/components/xform/styles/h5-designer.scss";
|
|
37
|
+
import "@base/components/xform/styles/h5-runtime.scss";
|
|
38
|
+
import "@base/components/hiprint/style.scss";
|
|
39
|
+
import "@base/components/wf/style.scss";
|
|
40
|
+
|
|
41
|
+
// 区域选取/复制粘贴:自研实现,通过 window.VXETableMixin 挂载,
|
|
42
|
+
// 必须在下面的 Vue.use(VXETable) 之前求值(core 在 install 时读取一次并 delete)
|
|
43
|
+
import "@base/components/table/plugins/cell-area/index.js";
|
|
44
|
+
import "@base/components/table/plugins/cell-area/index.scss";
|
|
45
|
+
|
|
46
|
+
// 引入扩展插件(官方付费插件,已由上面的自研 cell-area 替代,保留仅作回退参考)
|
|
47
|
+
// import "@base/components/table/plugins/extend-cell-area/vxe-table-extend-cell-area.es6.min.js";
|
|
48
|
+
// import "@base/components/table/plugins/extend-cell-area/vxe-table-extend-cell-area.min.css";
|
|
49
|
+
|
|
50
|
+
/* // 设置授权信息
|
|
51
|
+
VXETable.setup({
|
|
52
|
+
// showAuthLog: true, // 是否在控制台显示授权信息,专业版支持关闭
|
|
53
|
+
// authId: 'gbeumewkoyt2rhf9', // 获取授权后在官网登录后进入“用户中心”查看
|
|
54
|
+
authId: 'gbeumewkoyt2rhft', // 获取授权后在官网登录后进入“用户中心”查看
|
|
55
|
+
// onAuth (e) {
|
|
56
|
+
// // 打印授权状态
|
|
57
|
+
// console.log(e)
|
|
58
|
+
// }
|
|
59
|
+
}) */
|
|
60
|
+
|
|
61
|
+
import Vab from "@base/utils/vab";
|
|
62
|
+
import createCodeEditorAsyncComponent from "@base/components/code-editor/async";
|
|
63
|
+
|
|
64
|
+
Vue.use(Vab);
|
|
65
|
+
|
|
66
|
+
// appCode 兜底派生:子应用(拥有自己的子命名空间,即 WEB_PREFIX !== MAIN_WEB_PREFIX)
|
|
67
|
+
// 若未在自身 settings.js 里维护 appCode,则从 WEB_PREFIX 去前导斜杠派生(/mkweb → mkweb),
|
|
68
|
+
// 免得每个子应用再手填一处、也不会写错。主应用/独立主站两前缀相同 → 保持空。
|
|
69
|
+
// 优先级:mount 里的 props.appCode > settings 显式值 > 此派生。window.WEB_PREFIX /
|
|
70
|
+
// MAIN_WEB_PREFIX 已由上面 vab 的 Object.assign(window, prefixConfig) 设好。
|
|
71
|
+
if (!settings.appCode) {
|
|
72
|
+
const webPrefix = window.WEB_PREFIX || "";
|
|
73
|
+
const mainWebPrefix = window.MAIN_WEB_PREFIX || "";
|
|
74
|
+
if (webPrefix && webPrefix !== mainWebPrefix) {
|
|
75
|
+
settings.appCode = webPrefix.replace(/^\/+/, "");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Vue.use(VXETable);
|
|
80
|
+
|
|
81
|
+
/* import tablePluns from "@/mixins/table/index.js"
|
|
82
|
+
VXETable.use(tablePluns) */
|
|
83
|
+
|
|
84
|
+
import "@base/components/table/index.js";
|
|
85
|
+
|
|
86
|
+
import drag from "@base/directive/el-drag-dialog/index.js";
|
|
87
|
+
|
|
88
|
+
Vue.use(drag.install);
|
|
89
|
+
|
|
90
|
+
import elDialogCenter from "@base/directive/el-dialog-center/index.js";
|
|
91
|
+
|
|
92
|
+
Vue.use(elDialogCenter.install);
|
|
93
|
+
|
|
94
|
+
import ErroreBox from "@base/components/errorMsg/index.js";
|
|
95
|
+
|
|
96
|
+
Vue.use(ErroreBox);
|
|
97
|
+
|
|
98
|
+
import ExcelExport from "@base/components/excelExport/index.js";
|
|
99
|
+
|
|
100
|
+
Vue.use(ExcelExport);
|
|
101
|
+
|
|
102
|
+
// 同步入口:不走 lazyPrototypePlugin,启动时直接注册
|
|
103
|
+
import ExcelImport from "@base/components/excelImport/index.js";
|
|
104
|
+
|
|
105
|
+
Vue.use(ExcelImport);
|
|
106
|
+
|
|
107
|
+
import ConfirmDialog from "@base/components/confirmDialog/index.js";
|
|
108
|
+
|
|
109
|
+
Vue.use(ConfirmDialog);
|
|
110
|
+
|
|
111
|
+
import baseAlert from "@base/components/baseAlert/index.js";
|
|
112
|
+
|
|
113
|
+
Vue.use(baseAlert);
|
|
114
|
+
|
|
115
|
+
import FormDialog from "@base/components/formDialog/index.js";
|
|
116
|
+
|
|
117
|
+
Vue.use(FormDialog);
|
|
118
|
+
|
|
119
|
+
import FormDrawer from "@base/components/formDrawer/index.js";
|
|
120
|
+
|
|
121
|
+
Vue.use(FormDrawer);
|
|
122
|
+
|
|
123
|
+
import permission from "@base/directive/permission";
|
|
124
|
+
|
|
125
|
+
Vue.use(permission);
|
|
126
|
+
|
|
127
|
+
import VabUpload from "@base/components/VabUpload/index.js";
|
|
128
|
+
|
|
129
|
+
Vue.use(VabUpload);
|
|
130
|
+
|
|
131
|
+
Vue.component("baseUpload", () =>
|
|
132
|
+
import("@base/components/VabUpload/view.vue")
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
import baseAttachment from "@base/components/baseAttachment/install";
|
|
136
|
+
|
|
137
|
+
Vue.use(baseAttachment);
|
|
138
|
+
Vue.component("attachmentShowList", () =>
|
|
139
|
+
import("@base/components/baseAttachment/showList")
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
import "@base/core-resources/js/base/common.js";
|
|
143
|
+
|
|
144
|
+
import vxeFilter from "@base/components/table/vxeFilter/index.js";
|
|
145
|
+
|
|
146
|
+
Vue.use(vxeFilter);
|
|
147
|
+
|
|
148
|
+
// 首屏优化:低频/非首屏组件转懒注册(名字与组件内 name 一致,写死字符串)
|
|
149
|
+
Vue.component("base-table-export", () =>
|
|
150
|
+
import("@base/components/excelExport/button.vue")
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/* Vue.component('extendedProperties', () => import('@base/components/extendedProperties/view.vue')); */
|
|
154
|
+
|
|
155
|
+
import LimitNumber from "@base/directive/LimitNumber/index.js";
|
|
156
|
+
|
|
157
|
+
Vue.use(LimitNumber);
|
|
158
|
+
|
|
159
|
+
import baseInputNumber from "@base/components/baseInputNumber/index.vue";
|
|
160
|
+
|
|
161
|
+
Vue.component(baseInputNumber.name, baseInputNumber);
|
|
162
|
+
|
|
163
|
+
import elReadonly from "@base/directive/el-readonly/index.js";
|
|
164
|
+
|
|
165
|
+
Vue.use(elReadonly);
|
|
166
|
+
|
|
167
|
+
import appendToBody from "@base/directive/append-to-body/index.js";
|
|
168
|
+
|
|
169
|
+
Vue.use(appendToBody.install);
|
|
170
|
+
|
|
171
|
+
/*import cnPrint from "@base/components/cnPrint/index.js"
|
|
172
|
+
|
|
173
|
+
Vue.use(cnPrint);*/
|
|
174
|
+
|
|
175
|
+
Vue.component("oplogTable", () =>
|
|
176
|
+
import("@base/components/oplogTable/index.vue")
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
Vue.component("baseInputBatch", () =>
|
|
180
|
+
import("@base/components/baseInputBatch/index.vue")
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
Vue.component("baseArea", () => import("@base/components/baseArea/index.vue"));
|
|
184
|
+
|
|
185
|
+
Vue.component("baseInputExport", () =>
|
|
186
|
+
import("@base/components/baseInputExport/index.vue")
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
Vue.component("advancedSearchDialog", () =>
|
|
190
|
+
import("@base/components/advancedSearchDialog/index.vue")
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// baseTabs/baseTabPane 必须同步注册,不可懒注册:
|
|
194
|
+
// xform 详情容器(detail-item)把所有 detail-pane 及其内部字段组件放在 <baseTabs> 的默认插槽里。
|
|
195
|
+
// 异步组件未解析时 Vue 只渲染占位注释节点,插槽内的组件不会被实例化,
|
|
196
|
+
// 于是 detail-item 的 mounted 早于内部字段组件的 created(registerToRefList)执行,
|
|
197
|
+
// 模板脚本 onMounted 里的 getWidgetRef('xxx') 首次进入必然拿到 undefined(二次进入因异步工厂已缓存而正常)。
|
|
198
|
+
import baseTabs from "@base/components/baseTabs/index.vue";
|
|
199
|
+
import baseTabPane from "@base/components/baseTabs/baseTabPane.vue";
|
|
200
|
+
|
|
201
|
+
Vue.component(baseTabs.name, baseTabs);
|
|
202
|
+
Vue.component(baseTabPane.name, baseTabPane);
|
|
203
|
+
|
|
204
|
+
import vb_tabs from "@base/components/vb-tabs/index.vue";
|
|
205
|
+
import vb_tab_pane from "@base/components/vb-tabs/tab-pane.vue";
|
|
206
|
+
import xTabs from "@base/components/vb-tabs/x-tabs.vue";
|
|
207
|
+
|
|
208
|
+
Vue.component(vb_tabs.name, vb_tabs);
|
|
209
|
+
Vue.component(vb_tab_pane.name, vb_tab_pane);
|
|
210
|
+
Vue.component(xTabs.name, xTabs);
|
|
211
|
+
|
|
212
|
+
Vue.component("status-tag", () =>
|
|
213
|
+
import("@base/components/statusTag/index.vue")
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
Vue.component("CodeEditor", createCodeEditorAsyncComponent);
|
|
217
|
+
|
|
218
|
+
Vue.component("scriptDescriptionButton", () =>
|
|
219
|
+
import("@base/components/scriptDescription/button")
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
Vue.component("scriptTestButton", () =>
|
|
223
|
+
import("@base/components/scriptTest/button")
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
Vue.component("BaseTextarea", () =>
|
|
227
|
+
import("@base/components/base-textarea/index.vue")
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
import JsonImport from "@base/components/jsonImport/index.js";
|
|
231
|
+
|
|
232
|
+
Vue.use(JsonImport);
|
|
233
|
+
|
|
234
|
+
import LangImport from "@base/components/langImport/index.js";
|
|
235
|
+
|
|
236
|
+
Vue.use(LangImport);
|
|
237
|
+
|
|
238
|
+
//vform
|
|
239
|
+
// import '@base/components/xform/utils/directive'
|
|
240
|
+
|
|
241
|
+
import i18n from "@base/lang/index.js";
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* If you don't want to use mock-server
|
|
245
|
+
* you want to use MockJs for mock api
|
|
246
|
+
* you can execute: mockXHR()
|
|
247
|
+
*
|
|
248
|
+
* Currently MockJs will be used in the production environment,
|
|
249
|
+
* please remove it before going online ! ! !
|
|
250
|
+
*/
|
|
251
|
+
if (process.env.NODE_ENV === "production") {
|
|
252
|
+
/* const { mockXHR } = require('../mock')
|
|
253
|
+
mockXHR() */
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
Vue.use(Element, {
|
|
257
|
+
size: Cookies.get("size") || "medium", // set element-ui default size
|
|
258
|
+
// locale: enLang // 如果使用中文,无需设置,请删除
|
|
259
|
+
});
|
|
260
|
+
installRuntimeRecovery();
|
|
261
|
+
|
|
262
|
+
// register global utility filters
|
|
263
|
+
Object.keys(filters).forEach((key) => {
|
|
264
|
+
Vue.filter(key, filters[key]);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// 详情承载宿主:弹框 remote 分支用它 loadMicroApp 装载归属子应用渲染私有详情页
|
|
268
|
+
Vue.component("micro-view-host", () =>
|
|
269
|
+
import("@base/components/micro-view-host/index.vue")
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
Vue.config.productionTip = false;
|
|
273
|
+
|
|
274
|
+
let instance = null;
|
|
275
|
+
|
|
276
|
+
function render(props = {}) {
|
|
277
|
+
const { container } = props;
|
|
278
|
+
instance = new Vue({
|
|
279
|
+
router,
|
|
280
|
+
store,
|
|
281
|
+
i18n,
|
|
282
|
+
render: (h) => h(App),
|
|
283
|
+
});
|
|
284
|
+
// Synchronous child renders may call global helpers such as $t2.
|
|
285
|
+
// Publish the initialized root before mounting any child component.
|
|
286
|
+
window.$vueRoot = instance;
|
|
287
|
+
instance.$mount(container ? container.querySelector("#app") : "#app");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 详情承载入口(qiankun 详情承载协议 §5.5)。
|
|
292
|
+
* 主应用/渲染方的 micro-view-host 用 loadMicroApp 装载本应用并置 props.microDetailHost=true,
|
|
293
|
+
* 要求只渲染 targetPath 指向的详情页、把详情页的刷新/关闭/回调桥接回主应用,不出整站壳。
|
|
294
|
+
* 契约字段见 docs/qiankun详情承载子应用对接清单.md。
|
|
295
|
+
*
|
|
296
|
+
* ⚠ parentVue shim 的委托语义需真机联调打磨:详情页 getTarget(this) 命中 shim 后会用到
|
|
297
|
+
* $http/$message/$baseConfirm/$attrs 等——这些经原型委托到宿主根实例(全局插件已装齐),
|
|
298
|
+
* 仅 $baseReload / _handleCallback 覆盖为回主应用的动作。个别详情页若有更深的 $parent 依赖,
|
|
299
|
+
* 按对接清单 §5.6 做最小改造。
|
|
300
|
+
*/
|
|
301
|
+
async function mountDetailHost(props = {}) {
|
|
302
|
+
if (props.appCode) settings.appCode = props.appCode;
|
|
303
|
+
if (props.token) {
|
|
304
|
+
setAuthToken(props.token);
|
|
305
|
+
store.commit("user/SET_TOKEN", props.token);
|
|
306
|
+
}
|
|
307
|
+
if (props.lang) i18n.locale = props.lang;
|
|
308
|
+
|
|
309
|
+
const appCode = props.appCode || settings.appCode;
|
|
310
|
+
const resolved = await loadViewComponentAsync(props.targetPath, { appCode });
|
|
311
|
+
const target = resolved.component;
|
|
312
|
+
const bizParams = props.bizParams || {};
|
|
313
|
+
const viewMeta = props.viewMeta || {};
|
|
314
|
+
|
|
315
|
+
// shim 需先于 render 存在(作为 parentVue prop 传入),其 Vue 能力在挂载后经原型委托到根实例
|
|
316
|
+
const shim = {
|
|
317
|
+
$baseReload: () => props.onReload && props.onReload(),
|
|
318
|
+
_handleCallback: (flag) => props.onHandleCallback && props.onHandleCallback(flag),
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const { container } = props;
|
|
322
|
+
const mountEl = container ? container.querySelector("#app") : "#app";
|
|
323
|
+
instance = new Vue({
|
|
324
|
+
store,
|
|
325
|
+
i18n,
|
|
326
|
+
render(h) {
|
|
327
|
+
if (!target) {
|
|
328
|
+
return h(
|
|
329
|
+
"div",
|
|
330
|
+
{ staticClass: "micro-detail-host-missing" },
|
|
331
|
+
"详情页组件未找到:" + (props.targetPath || "")
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return h(target, {
|
|
335
|
+
props: Object.assign({}, bizParams, viewMeta, {
|
|
336
|
+
parentVue: shim,
|
|
337
|
+
_handleCallback: (flag) =>
|
|
338
|
+
props.onHandleCallback && props.onHandleCallback(flag),
|
|
339
|
+
outParam: props.initialOutParam,
|
|
340
|
+
}),
|
|
341
|
+
on: {
|
|
342
|
+
reload: () => props.onReload && props.onReload(),
|
|
343
|
+
close: () => props.onClose && props.onClose(),
|
|
344
|
+
"update:outParam": (v) =>
|
|
345
|
+
props.onUpdateOutParam && props.onUpdateOutParam(v),
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
window.$vueRoot = instance;
|
|
351
|
+
instance.$mount(mountEl);
|
|
352
|
+
// #4 listeners 透传:详情页里父级 v-on="listeners" 那批是详情页直接 this.$emit 的,
|
|
353
|
+
// 拦截根子组件的 $emit,任意事件都转发给主应用的 onEmit(按名字派回原 option.on)。
|
|
354
|
+
const child = instance.$children && instance.$children[0];
|
|
355
|
+
if (child && typeof child.$emit === "function") {
|
|
356
|
+
const rawEmit = child.$emit.bind(child);
|
|
357
|
+
child.$emit = (name, ...args) => {
|
|
358
|
+
if (typeof props.onEmit === "function") props.onEmit(name, ...args);
|
|
359
|
+
return rawEmit(name, ...args);
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
// 挂载后把 shim 的原型指向根实例:$http/$message/$baseConfirm 等委托过去,
|
|
363
|
+
// 而 shim 自有的 $baseReload/_handleCallback 覆盖生效(详情页动作时才会调用)
|
|
364
|
+
Object.setPrototypeOf(shim, instance);
|
|
365
|
+
window.$vueRoot = instance;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!window.__POWERED_BY_QIANKUN__) {
|
|
369
|
+
// 独立运行 / 主应用形态:行为与拆分前一致
|
|
370
|
+
render();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* qiankun 子应用生命周期。
|
|
375
|
+
* 引包子应用入口 re-export 即可:export * from "cloud-web-corejs/src/index";
|
|
376
|
+
* 主子通信协议见 docs/qiankun微前端改造-实现方案.md 3.3。
|
|
377
|
+
*/
|
|
378
|
+
export async function bootstrap() {}
|
|
379
|
+
|
|
380
|
+
export async function mount(props = {}) {
|
|
381
|
+
if (props.microDetailHost) {
|
|
382
|
+
// 详情承载模式(micro-view-host 装载):不走整站壳/路由/菜单,
|
|
383
|
+
// 只在容器里渲染 props.targetPath 指向的本应用组件。见对接清单文档。
|
|
384
|
+
return mountDetailHost(props);
|
|
385
|
+
}
|
|
386
|
+
if (props.appCode) {
|
|
387
|
+
// 主应用可经 props 覆盖,子应用也可在自身 settings.js 里声明
|
|
388
|
+
settings.appCode = props.appCode;
|
|
389
|
+
}
|
|
390
|
+
if (props.token) {
|
|
391
|
+
// token 存 sessionStorage,按 origin 隔离——跨域/跨端口主应用的登录态子应用天然拿不到,
|
|
392
|
+
// 主应用显式下发 token 即可免登(写入自身登录态后守卫按已登录走 getInfo2)
|
|
393
|
+
setAuthToken(props.token);
|
|
394
|
+
store.commit("user/SET_TOKEN", props.token);
|
|
395
|
+
}
|
|
396
|
+
store.commit("micro/SET_MICRO_CONTEXT", {
|
|
397
|
+
appCode: props.appCode || settings.appCode,
|
|
398
|
+
// 非数组(null/未传)= 自治嵌入模式:子应用自拉菜单、保留自身壳(micro store 据此置 selfManaged)
|
|
399
|
+
menus: Array.isArray(props.menus) ? props.menus : null,
|
|
400
|
+
mainAdapter: props.mainAdapter || null,
|
|
401
|
+
// 主应用经本地调试覆盖装载:允许 URL 直达未配菜单的页面
|
|
402
|
+
debug: !!props.microDebug,
|
|
403
|
+
});
|
|
404
|
+
// 再次挂载时 unmount 已重置路由,而 roles 仍在会使守卫跳过路由生成,这里主动补一次
|
|
405
|
+
if (store.getters.roles && store.getters.roles.length > 0) {
|
|
406
|
+
if (store.state.micro.selfManaged) {
|
|
407
|
+
await store.dispatch("permission/generateRoutes2", {
|
|
408
|
+
flag: store.state.user.userFlag,
|
|
409
|
+
});
|
|
410
|
+
} else {
|
|
411
|
+
await store.dispatch(
|
|
412
|
+
"permission/generateRoutesFromProps",
|
|
413
|
+
props.menus || []
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
render(props);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 保活协议(主应用 loadMicroApp 模式):
|
|
422
|
+
* update({ microActive: false }) —— 容器被隐藏:暂停路由监听(守卫 next(false) +
|
|
423
|
+
* ensureURL 置空,防止对主应用的 URL 变化做出反应、回写 URL 互相打架);
|
|
424
|
+
* update({ microActive: true }) —— 容器重新显示:恢复监听并把当前 hash 同步进路由
|
|
425
|
+
* (含主应用在隐藏期间切到本应用另一菜单的深导航场景)。
|
|
426
|
+
*/
|
|
427
|
+
let pausedEnsureURL = null;
|
|
428
|
+
|
|
429
|
+
function pauseSubRouter() {
|
|
430
|
+
if (store.state.micro.routerPaused) return;
|
|
431
|
+
store.commit("micro/SET_ROUTER_PAUSED", true);
|
|
432
|
+
const history = router.history;
|
|
433
|
+
if (history && typeof history.ensureURL === "function") {
|
|
434
|
+
pausedEnsureURL = history.ensureURL;
|
|
435
|
+
history.ensureURL = function () {};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function resumeSubRouter() {
|
|
440
|
+
if (store.state.micro.routerPaused) {
|
|
441
|
+
store.commit("micro/SET_ROUTER_PAUSED", false);
|
|
442
|
+
if (pausedEnsureURL) {
|
|
443
|
+
router.history.ensureURL = pausedEnsureURL;
|
|
444
|
+
pausedEnsureURL = null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
syncSubRouter();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function syncSubRouter() {
|
|
451
|
+
const current = window.location.hash.replace(/^#/, "") || "/";
|
|
452
|
+
if (router.currentRoute.fullPath !== current) {
|
|
453
|
+
// vue-router 3.0.2 的 push 不返回 Promise(3.1.0 才加),直接 .catch 会读到
|
|
454
|
+
// undefined 抛错,且此调用发生在 qiankun update 生命周期内——异常会让 parcel 直接
|
|
455
|
+
// died、后续所有 update(含页签刷新转发)失效。仅当 push 返回 Promise 时才吞异常。
|
|
456
|
+
const nav = router.push(current);
|
|
457
|
+
if (nav && typeof nav.catch === "function") {
|
|
458
|
+
nav.catch(() => {});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* 主应用页签"刷新"转发过来时在子应用内部重建当前页组件。
|
|
465
|
+
* 不能套用主应用那招(delCachedView + 跳 /redirect):子应用与主应用共用同一个
|
|
466
|
+
* 浏览器 hash,跳 /redirect 会被主应用 afterEach 判成"离开子应用路由",
|
|
467
|
+
* 容器随即隐藏并暂停子应用路由,重建被掐断——表现就是点刷新没反应。
|
|
468
|
+
* 改为自增序号通知 AppMain 原地销毁重建,全程不碰 URL。
|
|
469
|
+
*/
|
|
470
|
+
function refreshCurrentView() {
|
|
471
|
+
store.commit("micro/BUMP_RELOAD_SEQ");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export async function update(props = {}) {
|
|
475
|
+
if (props.microActive === false) {
|
|
476
|
+
pauseSubRouter();
|
|
477
|
+
} else if (props.microActive === true) {
|
|
478
|
+
resumeSubRouter();
|
|
479
|
+
}
|
|
480
|
+
if (props.microRefresh) {
|
|
481
|
+
refreshCurrentView();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export async function unmount() {
|
|
486
|
+
if (store.state.micro.routerPaused && pausedEnsureURL) {
|
|
487
|
+
router.history.ensureURL = pausedEnsureURL;
|
|
488
|
+
pausedEnsureURL = null;
|
|
489
|
+
}
|
|
490
|
+
if (instance) {
|
|
491
|
+
instance.$destroy();
|
|
492
|
+
if (instance.$el) {
|
|
493
|
+
instance.$el.innerHTML = "";
|
|
494
|
+
}
|
|
495
|
+
instance = null;
|
|
496
|
+
}
|
|
497
|
+
if (window.$vueRoot) {
|
|
498
|
+
window.$vueRoot = null;
|
|
499
|
+
}
|
|
500
|
+
store.commit("micro/CLEAR_MICRO_CONTEXT");
|
|
501
|
+
resetRouter();
|
|
502
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<el-dialog
|
|
3
|
+
ref="pageDialog"
|
|
3
4
|
:visible.sync="showDialog"
|
|
4
5
|
v-el-drag-dialog
|
|
5
|
-
v-el-dialog-center
|
|
6
|
+
v-el-dialog-center="!isPageScope"
|
|
7
|
+
:class="{
|
|
8
|
+
'xform-page-dialog-wrapper': isPageScope,
|
|
9
|
+
'xform-page-form-dialog-wrapper': isPageScope,
|
|
10
|
+
'xform-page-dialog-wrapper--top': pageDialogHasTop,
|
|
11
|
+
}"
|
|
6
12
|
v-bind="dialogConfig"
|
|
7
13
|
@close="close"
|
|
8
14
|
>
|
|
@@ -13,7 +19,11 @@
|
|
|
13
19
|
v-bind="bodyConfig"
|
|
14
20
|
v-if="listHost"
|
|
15
21
|
>
|
|
16
|
-
<list-view-host
|
|
22
|
+
<list-view-host
|
|
23
|
+
:formCode="listFormCode"
|
|
24
|
+
:param="dialogParam"
|
|
25
|
+
:dialogScope="option.dialogScope"
|
|
26
|
+
/>
|
|
17
27
|
</div>
|
|
18
28
|
<div class="cont designer-view" v-bind="bodyConfig" id="containt" v-else>
|
|
19
29
|
<v-form-render
|
|
@@ -30,6 +40,7 @@
|
|
|
30
40
|
@reload="reload"
|
|
31
41
|
v-bind="formConfig"
|
|
32
42
|
:param="dialogParam"
|
|
43
|
+
:dialogScope="option.dialogScope"
|
|
33
44
|
:dataTableOption="dialogDataTableOption"
|
|
34
45
|
class="data-table_height"
|
|
35
46
|
@hook:mounted="handleFormMounted"
|
|
@@ -75,6 +86,10 @@
|
|
|
75
86
|
|
|
76
87
|
<script>
|
|
77
88
|
import VFormRender from "../../../../../components/xform/form-render/index.vue";
|
|
89
|
+
import {
|
|
90
|
+
pageScopedDialogMixin,
|
|
91
|
+
pageDialogTop,
|
|
92
|
+
} from "@base/components/xform/form-render/pageDialogScope";
|
|
78
93
|
import {
|
|
79
94
|
resolveActionButtons,
|
|
80
95
|
hasButtonPermission,
|
|
@@ -95,7 +110,7 @@ export default {
|
|
|
95
110
|
visiable: Boolean,
|
|
96
111
|
option: Object,
|
|
97
112
|
},
|
|
98
|
-
mixins: [],
|
|
113
|
+
mixins: [pageScopedDialogMixin],
|
|
99
114
|
inject: ["getFormConfig"],
|
|
100
115
|
|
|
101
116
|
mounted() {},
|
|
@@ -171,6 +186,15 @@ export default {
|
|
|
171
186
|
config = Object.assign({}, config, this.option.dialogConfig, {
|
|
172
187
|
customClass,
|
|
173
188
|
});
|
|
189
|
+
if (this.isPageScope) {
|
|
190
|
+
Object.assign(config, {
|
|
191
|
+
appendToBody: false,
|
|
192
|
+
modalAppendToBody: false,
|
|
193
|
+
modal: false,
|
|
194
|
+
lockScroll: false,
|
|
195
|
+
top: pageDialogTop(this.option.dialogConfig?.top),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
174
198
|
config.title = this.$t1(config.title);
|
|
175
199
|
return config;
|
|
176
200
|
},
|
|
@@ -236,7 +260,9 @@ export default {
|
|
|
236
260
|
method: `post`,
|
|
237
261
|
data: data,
|
|
238
262
|
isLoading: true,
|
|
239
|
-
loadingTarget:
|
|
263
|
+
loadingTarget: this.isPageScope
|
|
264
|
+
? this.option.pageDialogHost
|
|
265
|
+
: document.body,
|
|
240
266
|
modalStrictly: true,
|
|
241
267
|
success: (res) => {
|
|
242
268
|
let formTemplate = res.objx || {};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<el-dialog
|
|
3
|
+
ref="pageDialog"
|
|
3
4
|
:visible.sync="showDialog"
|
|
4
5
|
v-el-drag-dialog
|
|
5
|
-
v-el-dialog-center
|
|
6
|
+
v-el-dialog-center="!isPageScope"
|
|
7
|
+
:class="{
|
|
8
|
+
'xform-page-dialog-wrapper': isPageScope,
|
|
9
|
+
'xform-page-dialog-wrapper--top': pageDialogHasTop,
|
|
10
|
+
}"
|
|
6
11
|
v-bind="dialogConfig"
|
|
7
12
|
@close="close"
|
|
8
13
|
>
|
|
@@ -22,6 +27,7 @@
|
|
|
22
27
|
:dataTableOption="dataTableOption"
|
|
23
28
|
v-bind="formConfig"
|
|
24
29
|
:param="dialogParam"
|
|
30
|
+
:dialogScope="option.dialogScope"
|
|
25
31
|
class="data-table_height labMargin"
|
|
26
32
|
/>
|
|
27
33
|
</div>
|
|
@@ -72,12 +78,16 @@
|
|
|
72
78
|
<script>
|
|
73
79
|
import { selectDialogMixins } from "../../../../../mixins/selectDialog/index.js";
|
|
74
80
|
import VFormRender from "../../../../../components/xform/form-render/index.vue";
|
|
81
|
+
import {
|
|
82
|
+
pageScopedDialogMixin,
|
|
83
|
+
pageDialogTop,
|
|
84
|
+
} from "@base/components/xform/form-render/pageDialogScope";
|
|
75
85
|
import { traverseAllWidgetsNew } from "../../../../../components/xform/utils/util";
|
|
76
86
|
|
|
77
87
|
export default {
|
|
78
88
|
components: { VFormRender },
|
|
79
89
|
props: ["visiable", "multi", "rows", "param", "widget", "option"],
|
|
80
|
-
mixins: [selectDialogMixins],
|
|
90
|
+
mixins: [selectDialogMixins, pageScopedDialogMixin],
|
|
81
91
|
inject: ["getFormConfig"],
|
|
82
92
|
mounted() {},
|
|
83
93
|
data() {
|
|
@@ -172,6 +182,15 @@ export default {
|
|
|
172
182
|
config = Object.assign({}, config, this.option.dialogConfig, {
|
|
173
183
|
customClass,
|
|
174
184
|
});
|
|
185
|
+
if (this.isPageScope) {
|
|
186
|
+
Object.assign(config, {
|
|
187
|
+
appendToBody: false,
|
|
188
|
+
modalAppendToBody: false,
|
|
189
|
+
modal: false,
|
|
190
|
+
lockScroll: false,
|
|
191
|
+
top: pageDialogTop(this.option.dialogConfig?.top),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
175
194
|
config.title = this.$t1(config.title);
|
|
176
195
|
return config;
|
|
177
196
|
},
|
|
@@ -228,7 +247,9 @@ export default {
|
|
|
228
247
|
method: `post`,
|
|
229
248
|
data: data,
|
|
230
249
|
isLoading: true,
|
|
231
|
-
loadingTarget:
|
|
250
|
+
loadingTarget: this.isPageScope
|
|
251
|
+
? this.option.pageDialogHost
|
|
252
|
+
: document.body,
|
|
232
253
|
modalStrictly: true,
|
|
233
254
|
success: (res) => {
|
|
234
255
|
let formTemplate = res.objx || {};
|
|
@@ -300,7 +321,9 @@ export default {
|
|
|
300
321
|
tableAlias: this.formCode,
|
|
301
322
|
},
|
|
302
323
|
isLoading: true,
|
|
303
|
-
loadingTarget:
|
|
324
|
+
loadingTarget: this.isPageScope
|
|
325
|
+
? this.option.pageDialogHost
|
|
326
|
+
: document.body,
|
|
304
327
|
modalStrictly: true,
|
|
305
328
|
success: (res) => {
|
|
306
329
|
let formData = res.objx && res.objx.data ? res.objx.data : {};
|
|
@@ -11,16 +11,23 @@
|
|
|
11
11
|
:clearable="field.options.clearable"
|
|
12
12
|
:minlength="field.options.minLength" :maxlength="field.options.maxLength"
|
|
13
13
|
:show-word-limit="field.options.showWordLimit"
|
|
14
|
-
:prefix-icon="field.options.prefixIcon" :suffix-icon="field.options.suffixIcon"
|
|
14
|
+
:prefix-icon="field.options.prefixIcon" :suffix-icon="clickableSuffix ? '' : field.options.suffixIcon"
|
|
15
15
|
@focus="handleFocusCustomEvent" @blur="handleBlurCustomEvent" @input="handleInputCustomEvent"
|
|
16
16
|
@change="handleChangeEvent"
|
|
17
17
|
:style="'width:'+field.options.widgetWidth+' !important;'"
|
|
18
18
|
:class="{'custom-width':field.options.widgetWidth!=null}">
|
|
19
19
|
<el-button slot="append" v-if="field.options.appendButton" :disabled="field.options.disabled || field.options.appendButtonDisabled"
|
|
20
20
|
:class="field.options.buttonIcon" @click.native="emitAppendButtonClick"></el-button>
|
|
21
|
+
<i v-if="clickableSuffix" slot="suffix"
|
|
22
|
+
:class="field.options.suffixIcon"
|
|
23
|
+
class="el-input__icon suffix-icon-clickable"
|
|
24
|
+
@click.stop="handleSuffixIconClick"></i>
|
|
21
25
|
</el-input>
|
|
22
26
|
<template v-if="isReadMode">
|
|
23
27
|
<span class="readonly-mode-field pre-wrap" :title="contentForReadMode">{{contentForReadMode}}</span>
|
|
28
|
+
<i v-if="readModeSuffix" :class="field.options.suffixIcon"
|
|
29
|
+
class="el-input__icon suffix-icon-clickable"
|
|
30
|
+
@click.stop="handleSuffixIconClick"></i>
|
|
24
31
|
</template>
|
|
25
32
|
</form-item-wrapper>
|
|
26
33
|
</template>
|
|
@@ -82,6 +89,18 @@
|
|
|
82
89
|
contentForReadMode() {
|
|
83
90
|
return this.fieldModel ? this.fieldModel : ''
|
|
84
91
|
},
|
|
92
|
+
clickableSuffix() {
|
|
93
|
+
//尾部图标可点击(编辑态):开关开启且已配置suffixIcon;开启后suffix-icon改由slot渲染,避免图标重复
|
|
94
|
+
return !!this.field.options.suffixIconClickable
|
|
95
|
+
&& !!this.field.options.suffixIcon
|
|
96
|
+
&& !this.isReadMode
|
|
97
|
+
},
|
|
98
|
+
readModeSuffix() {
|
|
99
|
+
//只读模式:el-input不渲染,图标单独补充在只读文本旁
|
|
100
|
+
return !!this.field.options.suffixIconClickable
|
|
101
|
+
&& !!this.field.options.suffixIcon
|
|
102
|
+
&& this.isReadMode
|
|
103
|
+
},
|
|
85
104
|
|
|
86
105
|
},
|
|
87
106
|
beforeCreate() {
|
|
@@ -108,7 +127,11 @@
|
|
|
108
127
|
},
|
|
109
128
|
|
|
110
129
|
methods: {
|
|
111
|
-
|
|
130
|
+
handleSuffixIconClick() {
|
|
131
|
+
if (this.designState) return
|
|
132
|
+
//复用后置按钮的点击链:onAppendButtonClick脚本 → dispatch('appendButtonClick') → handleCustomClickEvent
|
|
133
|
+
this.emitAppendButtonClick()
|
|
134
|
+
}
|
|
112
135
|
}
|
|
113
136
|
}
|
|
114
137
|
</script>
|
|
@@ -116,4 +139,7 @@
|
|
|
116
139
|
<style lang="scss" scoped>
|
|
117
140
|
@import "~@base/styles/theme-variables.scss"; //* form-item-wrapper已引入,还需要重复引入吗? *//
|
|
118
141
|
|
|
142
|
+
.suffix-icon-clickable {
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
}
|
|
119
145
|
</style>
|
|
@@ -9,11 +9,24 @@
|
|
|
9
9
|
@submit.native.prevent
|
|
10
10
|
>
|
|
11
11
|
<el-collapse v-model="formActiveCollapseNames" class="setting-collapse">
|
|
12
|
-
<el-collapse-item
|
|
13
|
-
name="1"
|
|
14
|
-
:title="i18nt('designer.setting.basicSetting')"
|
|
15
|
-
>
|
|
16
|
-
<el-form-item
|
|
12
|
+
<el-collapse-item
|
|
13
|
+
name="1"
|
|
14
|
+
:title="i18nt('designer.setting.basicSetting')"
|
|
15
|
+
>
|
|
16
|
+
<el-form-item
|
|
17
|
+
v-if="formConfig.layoutType === 'PC'"
|
|
18
|
+
class="form-item-label-top"
|
|
19
|
+
label="业务弹框作用域"
|
|
20
|
+
>
|
|
21
|
+
<el-radio-group v-model="dialogScopeValue">
|
|
22
|
+
<el-radio label="global">全局</el-radio>
|
|
23
|
+
<el-radio label="page">当前页面</el-radio>
|
|
24
|
+
</el-radio-group>
|
|
25
|
+
<div style="line-height: 1.6; color: #909399; font-size: 12px">
|
|
26
|
+
列表和详情模板需分别设置。当前页面模式只遮挡表单内容,顶部页签和左侧菜单仍可操作。
|
|
27
|
+
</div>
|
|
28
|
+
</el-form-item>
|
|
29
|
+
<el-form-item class="form-item-label-top" :label="i18nt('数据库表')">
|
|
17
30
|
<el-input type="text" v-model="formConfig.entity"></el-input>
|
|
18
31
|
</el-form-item>
|
|
19
32
|
<el-form-item class="form-item-label-top" :label="i18nt('数据保存脚本编码')">
|
|
@@ -1273,8 +1286,16 @@ $\{wf.starterName\}提交的$\{pm_product_project.product_code\}$\{pm_product_pr
|
|
|
1273
1286
|
this.designer.emitEvent("form-css-updated", deepClone(this.cssClassList));
|
|
1274
1287
|
});
|
|
1275
1288
|
},
|
|
1276
|
-
computed: {
|
|
1277
|
-
|
|
1289
|
+
computed: {
|
|
1290
|
+
dialogScopeValue: {
|
|
1291
|
+
get() {
|
|
1292
|
+
return this.formConfig.dialogScope === "page" ? "page" : "global";
|
|
1293
|
+
},
|
|
1294
|
+
set(value) {
|
|
1295
|
+
this.$set(this.formConfig, "dialogScope", value);
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
// formDialog/formDrawer 底部操作按钮配置(存 formConfig,随模板保存)
|
|
1278
1299
|
actionButtons() {
|
|
1279
1300
|
return this.formConfig.actionButtons || [];
|
|
1280
1301
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-form-item :label="i18nt('designer.setting.suffixIconClickable')">
|
|
3
|
+
<el-switch v-model="optionModel.suffixIconClickable"></el-switch>
|
|
4
|
+
</el-form-item>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import i18n from "../../../../../components/xform/utils/i18n"
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: "suffixIconClickable-editor",
|
|
12
|
+
mixins: [i18n],
|
|
13
|
+
props: {
|
|
14
|
+
designer: Object,
|
|
15
|
+
selectedWidget: Object,
|
|
16
|
+
optionModel: Object,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<style scoped>
|
|
22
|
+
|
|
23
|
+
</style>
|
|
@@ -211,6 +211,7 @@ const ADVANCED_PROPERTIES = {
|
|
|
211
211
|
// 'showWordLimit': 'showWordLimit-editor',
|
|
212
212
|
prefixIcon: "prefixIcon-editor",
|
|
213
213
|
suffixIcon: "suffixIcon-editor",
|
|
214
|
+
suffixIconClickable: "suffixIconClickable-editor",
|
|
214
215
|
switchWidth: "switchWidth-editor",
|
|
215
216
|
activeText: "activeText-editor",
|
|
216
217
|
inactiveText: "inactiveText-editor",
|
|
@@ -55,6 +55,7 @@ import html2Canvas from "html2canvas";
|
|
|
55
55
|
import JsPDF from "jspdf";
|
|
56
56
|
import formulaDialog from "@base/components/xform/form-designer/form-widget/dialog/formulaDialog.vue";
|
|
57
57
|
import baseFormulaDialog from "@base/components/xform/form-designer/form-widget/dialog/baseFormulaDialog.vue";
|
|
58
|
+
import { resolveDialogOption } from "@base/components/xform/form-render/pageDialogScope";
|
|
58
59
|
import QRCode from "qrcode";
|
|
59
60
|
import { openLuckysheetDialog } from "@base/components/luckysheet";
|
|
60
61
|
import {
|
|
@@ -168,6 +169,10 @@ modules = {
|
|
|
168
169
|
type: Boolean,
|
|
169
170
|
default: false,
|
|
170
171
|
},
|
|
172
|
+
dialogScope: {
|
|
173
|
+
type: String,
|
|
174
|
+
default: null,
|
|
175
|
+
},
|
|
171
176
|
},
|
|
172
177
|
provide() {
|
|
173
178
|
let e = this;
|
|
@@ -5042,14 +5047,24 @@ modules = {
|
|
|
5042
5047
|
}
|
|
5043
5048
|
},*/
|
|
5044
5049
|
openSearchDialog(option) {
|
|
5045
|
-
this.searchDialogOption =
|
|
5050
|
+
this.searchDialogOption = resolveDialogOption(
|
|
5051
|
+
option,
|
|
5052
|
+
this.formJsonObj?.formConfig,
|
|
5053
|
+
this.dialogScope,
|
|
5054
|
+
this.$el
|
|
5055
|
+
);
|
|
5046
5056
|
this.showSearchDialog = true;
|
|
5047
5057
|
},
|
|
5048
5058
|
confirmSearchDialog(rows) {
|
|
5049
5059
|
this.searchDialogOption.confirm && this.searchDialogOption.confirm(rows);
|
|
5050
5060
|
},
|
|
5051
5061
|
openFormDialog(option) {
|
|
5052
|
-
this.formDialogOption =
|
|
5062
|
+
this.formDialogOption = resolveDialogOption(
|
|
5063
|
+
option,
|
|
5064
|
+
this.formJsonObj?.formConfig,
|
|
5065
|
+
this.dialogScope,
|
|
5066
|
+
this.$el
|
|
5067
|
+
);
|
|
5053
5068
|
this.showFormDialog = true;
|
|
5054
5069
|
},
|
|
5055
5070
|
closeFormDialog() {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const PAGE_DIALOG_SCOPE = "page";
|
|
2
|
+
|
|
3
|
+
// 模板里的 top: "25vh" 是按整个窗口居中。当前页面弹框改由页面内容区 flex 居中,
|
|
4
|
+
// 再套视口 vh 会从内容区顶部再下移一截,弹框就偏下。px 等显式偏移仍保留。
|
|
5
|
+
export function pageDialogTop(top) {
|
|
6
|
+
const value = top == null ? "" : String(top).trim();
|
|
7
|
+
if (!value || /vh$/i.test(value)) return "0";
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const PAGE_HOST_SELECTOR = "[data-xform-page-dialog-host]";
|
|
12
|
+
|
|
13
|
+
export function resolveDialogOption(option, formConfig, inheritedScope, element) {
|
|
14
|
+
const source = option || {};
|
|
15
|
+
const scope = source.dialogScope ?? formConfig?.dialogScope ?? inheritedScope;
|
|
16
|
+
if (scope !== PAGE_DIALOG_SCOPE) return source;
|
|
17
|
+
|
|
18
|
+
const host = element?.closest?.(PAGE_HOST_SELECTOR);
|
|
19
|
+
if (!host) {
|
|
20
|
+
console.warn("xform page dialog host not found; using global dialog");
|
|
21
|
+
return { ...source, dialogScope: "global" };
|
|
22
|
+
}
|
|
23
|
+
return { ...source, dialogScope: PAGE_DIALOG_SCOPE, pageDialogHost: host };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const pageScopedDialogMixin = {
|
|
27
|
+
computed: {
|
|
28
|
+
isPageScope() {
|
|
29
|
+
return (
|
|
30
|
+
this.option?.dialogScope === PAGE_DIALOG_SCOPE
|
|
31
|
+
&& !!this.option.pageDialogHost
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
pageDialogHasTop() {
|
|
35
|
+
return this.isPageScope && pageDialogTop(this.option.dialogConfig?.top) !== "0";
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
mounted() {
|
|
39
|
+
if (!this.isPageScope) return;
|
|
40
|
+
const dialog = this.$refs.pageDialog;
|
|
41
|
+
const wrapper = dialog?.$el;
|
|
42
|
+
const host = this.option.pageDialogHost;
|
|
43
|
+
if (!wrapper || !host) return;
|
|
44
|
+
host.appendChild(wrapper);
|
|
45
|
+
this.pageDialogWrapper = wrapper;
|
|
46
|
+
// The surrounding menu and route tabs remain interactive.
|
|
47
|
+
dialog.$refs.dialog?.setAttribute("aria-modal", "false");
|
|
48
|
+
},
|
|
49
|
+
beforeDestroy() {
|
|
50
|
+
const wrapper = this.pageDialogWrapper;
|
|
51
|
+
if (wrapper?.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
52
|
+
this.pageDialogWrapper = null;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -299,6 +299,7 @@ export default {
|
|
|
299
299
|
showWordLimit: "Show Word Limit",
|
|
300
300
|
prefixIcon: "Prefix Icon",
|
|
301
301
|
suffixIcon: "Suffix Icon",
|
|
302
|
+
suffixIconClickable: "Clickable Suffix Icon",
|
|
302
303
|
inputButton: "Input Button Setting",
|
|
303
304
|
appendButton: "Append Button",
|
|
304
305
|
appendButtonDisabled: "Button Disabled",
|
|
@@ -3,8 +3,9 @@ let modules = {};
|
|
|
3
3
|
|
|
4
4
|
modules = {
|
|
5
5
|
install: function(Vue) {
|
|
6
|
-
Vue.directive('el-dialog-center', {
|
|
7
|
-
bind(el, binding, vnode) {
|
|
6
|
+
Vue.directive('el-dialog-center', {
|
|
7
|
+
bind(el, binding, vnode) {
|
|
8
|
+
if (binding.value === false) return;
|
|
8
9
|
if (el.children[0].querySelector(
|
|
9
10
|
".el-dialog__body")) {
|
|
10
11
|
let autoTop = (document.body.offsetHeight - el.children[0]
|
|
@@ -18,10 +18,39 @@ modules = {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
dialogHeaderEl.onmousedown = function(e) {
|
|
22
|
-
indexUtil.addClass(document.body, 'resize-ing');
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
dialogHeaderEl.onmousedown = function(e) {
|
|
22
|
+
indexUtil.addClass(document.body, 'resize-ing');
|
|
23
|
+
|
|
24
|
+
if (el.classList.contains('xform-page-dialog-wrapper')) {
|
|
25
|
+
const hostRect = el.parentNode.getBoundingClientRect();
|
|
26
|
+
const dialogRect = dragDom.getBoundingClientRect();
|
|
27
|
+
const startX = e.clientX;
|
|
28
|
+
const startY = e.clientY;
|
|
29
|
+
const startLeft = parseFloat(getStyle(dragDom, 'left')) || 0;
|
|
30
|
+
const startTop = parseFloat(getStyle(dragDom, 'top')) || 0;
|
|
31
|
+
|
|
32
|
+
document.onmousemove = function(moveEvent) {
|
|
33
|
+
const deltaX = Math.min(
|
|
34
|
+
Math.max(moveEvent.clientX - startX, hostRect.left - dialogRect.left),
|
|
35
|
+
hostRect.right - dialogRect.right
|
|
36
|
+
);
|
|
37
|
+
const deltaY = Math.min(
|
|
38
|
+
Math.max(moveEvent.clientY - startY, hostRect.top - dialogRect.top),
|
|
39
|
+
hostRect.bottom - dialogRect.bottom
|
|
40
|
+
);
|
|
41
|
+
dragDom.style.left = startLeft + deltaX + 'px';
|
|
42
|
+
dragDom.style.top = startTop + deltaY + 'px';
|
|
43
|
+
vnode.child.$emit('dragDialog');
|
|
44
|
+
};
|
|
45
|
+
document.onmouseup = function() {
|
|
46
|
+
indexUtil.removeClass(document.body, 'resize-ing');
|
|
47
|
+
document.onmousemove = null;
|
|
48
|
+
document.onmouseup = null;
|
|
49
|
+
};
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 鼠标按下,计算当前元素距离可视区的距离
|
|
25
54
|
let disX = e.clientX - dialogHeaderEl.offsetLeft;
|
|
26
55
|
let disY = e.clientY - dialogHeaderEl.offsetTop;
|
|
27
56
|
|
|
@@ -81,10 +81,7 @@ export const constantRoutes = [
|
|
|
81
81
|
path: "/haier/system/salesman/list",
|
|
82
82
|
component: () => import("@/views/haier/system/salesman/list.vue"),
|
|
83
83
|
name: "haierSalesmanPilot",
|
|
84
|
-
meta: {
|
|
85
|
-
title: "业务员",
|
|
86
|
-
icon: "user",
|
|
87
|
-
},
|
|
84
|
+
meta: { title: "业务员", icon: "user" },
|
|
88
85
|
},
|
|
89
86
|
],
|
|
90
87
|
},
|
|
@@ -78,6 +78,97 @@ body {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
.app-main #containt.xform-page-dialog-host {
|
|
83
|
+
z-index: 0;
|
|
84
|
+
isolation: isolate;
|
|
85
|
+
|
|
86
|
+
> .xform-page-dialog-wrapper {
|
|
87
|
+
position: absolute !important;
|
|
88
|
+
inset: 0;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
background: transparent;
|
|
93
|
+
overflow: auto !important;
|
|
94
|
+
|
|
95
|
+
&.xform-page-dialog-wrapper--top {
|
|
96
|
+
align-items: flex-start;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
> .el-dialog {
|
|
100
|
+
flex: none;
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-direction: column;
|
|
103
|
+
max-width: calc(100% - 24px);
|
|
104
|
+
max-height: calc(100% - 24px);
|
|
105
|
+
margin-bottom: 0;
|
|
106
|
+
|
|
107
|
+
&.is-fullscreen {
|
|
108
|
+
max-width: 100%;
|
|
109
|
+
max-height: 100%;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
> .el-dialog__body {
|
|
113
|
+
min-height: 0;
|
|
114
|
+
overflow: auto;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 业务表单原先用视口 vh 给 .cont 定高。挂进当前页面后,可用高度变小,
|
|
119
|
+
// 需要让标题、内容、底栏按弹框自身高度分配,只在内容区滚动。
|
|
120
|
+
&.xform-page-form-dialog-wrapper {
|
|
121
|
+
overflow: hidden !important;
|
|
122
|
+
|
|
123
|
+
> .el-dialog {
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
|
|
126
|
+
> .el-dialog__header,
|
|
127
|
+
> .el-dialog__footer {
|
|
128
|
+
flex: 0 0 auto;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
> .el-dialog__body {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex: 0 1 auto;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
height: auto !important;
|
|
136
|
+
min-height: 0;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
|
|
139
|
+
> .cont {
|
|
140
|
+
flex: 0 1 auto;
|
|
141
|
+
// 与全局业务弹框的常用高度一致;bodyConfig 的行内高度仍可覆盖。
|
|
142
|
+
height: 70vh;
|
|
143
|
+
min-height: 0;
|
|
144
|
+
overflow-x: hidden;
|
|
145
|
+
overflow-y: auto;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 与旧版全局业务弹框一致,内容四周保留白色留白。
|
|
149
|
+
// 用边框承载留白,滚动时它不会像 padding 一样被卷走。
|
|
150
|
+
> .cont.designer-view:not(.fd-list-host) {
|
|
151
|
+
box-sizing: border-box;
|
|
152
|
+
border: 12px solid #fff;
|
|
153
|
+
padding: 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 弹框挂进 #app 后,#containt.designer-view 的 background: none 会作用到弹框内容,
|
|
161
|
+
// 白底被去掉,露出页面底色。这里只把内容区白底加回来。
|
|
162
|
+
.el-dialog__body > #containt {
|
|
163
|
+
background-color: #fff;
|
|
164
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
|
|
165
|
+
|
|
166
|
+
&.designer-view {
|
|
167
|
+
background-color: #fff;
|
|
168
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
81
172
|
/* 外链页面(#/outLinkView、#/form_outLinkView 等)不走 layout,没有 .app-main 撑高度,
|
|
82
173
|
#containt 只能被内容撑开,列表/详情会塌陷显示不全,这里补上与 .app-main 一致的高度 */
|
|
83
174
|
#app > #containt {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
id="containt"
|
|
4
|
+
:class="{
|
|
5
|
+
'xform-page-dialog-host':
|
|
6
|
+
layoutType === 'PC' && !dialogScope && formConfig.dialogScope === 'page',
|
|
7
|
+
}"
|
|
8
|
+
:data-xform-page-dialog-host="
|
|
9
|
+
layoutType === 'PC' && !dialogScope && formConfig.dialogScope === 'page'
|
|
10
|
+
? ''
|
|
11
|
+
: null
|
|
12
|
+
"
|
|
13
|
+
>
|
|
3
14
|
<template v-if="layoutType === 'PC'">
|
|
4
15
|
<x-tabs
|
|
5
16
|
ref="xTabs"
|
|
@@ -22,6 +33,7 @@
|
|
|
22
33
|
visible-key="showEdit"
|
|
23
34
|
:visiable.sync="showEdit"
|
|
24
35
|
:parent-target="_self"
|
|
36
|
+
:dialogScope="dialogScope"
|
|
25
37
|
@reload="reload"
|
|
26
38
|
@openCopyEditTab="openCopyEditTab"
|
|
27
39
|
></vFormRender>
|
|
@@ -40,6 +52,7 @@
|
|
|
40
52
|
visible-key="showRender"
|
|
41
53
|
:visiable.sync="showRender"
|
|
42
54
|
:parent-target="_self"
|
|
55
|
+
:dialogScope="dialogScope"
|
|
43
56
|
@reload="$reloadHandle"
|
|
44
57
|
:openEditDialog="openEditDialog"
|
|
45
58
|
></vFormRender>
|
|
@@ -59,6 +72,7 @@
|
|
|
59
72
|
:param="item.param"
|
|
60
73
|
v-if="item.showContent"
|
|
61
74
|
:parent-target="_self"
|
|
75
|
+
:dialogScope="dialogScope"
|
|
62
76
|
@reload="(...args) => reloadAutoContent(...args)"
|
|
63
77
|
:openEditDialog="openEditDialog1"
|
|
64
78
|
></vFormRender>
|
|
@@ -78,6 +92,7 @@
|
|
|
78
92
|
:param="tab.param"
|
|
79
93
|
v-if="tab.showContent"
|
|
80
94
|
:parent-target="_self"
|
|
95
|
+
:dialogScope="dialogScope"
|
|
81
96
|
@reload="(...args) => onEditTabReload(tab, ...args)"
|
|
82
97
|
@openCopyEditTab="openCopyEditTab"
|
|
83
98
|
></vFormRender>
|
|
@@ -107,6 +122,7 @@
|
|
|
107
122
|
v-if="showEditDialog && showEdit"
|
|
108
123
|
visible-key="showEdit"
|
|
109
124
|
:parent-target="_self"
|
|
125
|
+
:dialogScope="dialogScope"
|
|
110
126
|
@reload="$reloadHandle"
|
|
111
127
|
></vFormRender>
|
|
112
128
|
</div>
|
|
@@ -118,6 +134,7 @@
|
|
|
118
134
|
v-if="showRender"
|
|
119
135
|
visible-key="showRender"
|
|
120
136
|
:parent-target="_self"
|
|
137
|
+
:dialogScope="dialogScope"
|
|
121
138
|
@reload="$reloadHandle"
|
|
122
139
|
:openEditDialog="openEditDialog"
|
|
123
140
|
></vFormRender>
|
|
@@ -131,6 +148,12 @@ import vFormRender from "../../../../views/user/form/vform/render";
|
|
|
131
148
|
export default {
|
|
132
149
|
name: "form_view:list",
|
|
133
150
|
components: { vFormRender },
|
|
151
|
+
props: {
|
|
152
|
+
dialogScope: {
|
|
153
|
+
type: String,
|
|
154
|
+
default: null,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
134
157
|
mounted() {},
|
|
135
158
|
data() {
|
|
136
159
|
let that = this;
|