cloud-web-corejs 1.0.54-dev.751 → 1.0.54-dev.754

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +3 -1
  2. package/src/App.vue +1 -1
  3. package/src/components/baseInputNumber/index.vue +9 -9
  4. package/src/components/errorMsg/index.js +47 -3
  5. package/src/components/errorMsg/mixins.js +101 -4
  6. package/src/components/jsonImport/mixins.js +9 -7
  7. package/src/components/langImport/mixins.js +9 -7
  8. package/src/components/mobile/file/index.vue +10 -0
  9. package/src/components/wf/wf.js +20 -2
  10. package/src/components/xform/form-designer/designer.js +1 -0
  11. package/src/components/xform/form-designer/form-widget/dialog/importDialogMixin.js +9 -7
  12. package/src/components/xform/form-designer/indexMixin.js +8 -1
  13. package/src/components/xform/form-designer/setting-panel/form-setting.vue +33 -2
  14. package/src/components/xform/form-designer/setting-panel/indexMixin.js +11 -1
  15. package/src/components/xform/form-designer/toolbar-panel/indexMixin.js +8 -1
  16. package/src/components/xform/form-designer/widget-panel/indexMixin.js +8 -1
  17. package/src/components/xform/form-render/container-item/detail-h5-item.vue +1 -0
  18. package/src/components/xform/form-render/container-item/detail-item.vue +1 -0
  19. package/src/components/xform/form-render/indexMixin.js +23 -14
  20. package/src/components/xform/utils/util.js +11 -5
  21. package/src/directive/LimitNumber/index.js +130 -124
  22. package/src/index.js +4 -0
  23. package/src/layout/components/AppMain.vue +9 -7
  24. package/src/layout/defaultLayout.vue +8 -2
  25. package/src/microRouter/index.js +37 -20
  26. package/src/router/modules/customer.js +9 -0
  27. package/src/utils/aes.js +21 -2
  28. package/src/utils/global.js +370 -8
  29. package/src/utils/keepAlive.js +2 -2
  30. package/src/utils/validate.js +104 -1
  31. package/src/views/support/form-dialog-demo/demoPickerDialog.vue +72 -0
  32. package/src/views/support/form-dialog-demo/index.vue +537 -0
  33. package/src/views/user/home/default2.vue +9 -2
  34. package/src/views/user/login/indexMixin.js +45 -11
@@ -0,0 +1,72 @@
1
+ <template>
2
+ <el-dialog
3
+ title="选择供应商(自定义 Vue 弹框)"
4
+ :visible.sync="showDialog"
5
+ width="560px"
6
+ :close-on-click-modal="false"
7
+ :append-to-body="true"
8
+ custom-class="dialog-style"
9
+ v-el-drag-dialog
10
+ @close="handleClose"
11
+ >
12
+ <el-table
13
+ :data="rows"
14
+ border
15
+ highlight-current-row
16
+ @current-change="handleCurrentChange"
17
+ @row-dblclick="handleConfirm"
18
+ >
19
+ <el-table-column prop="id" label="编号" width="100" />
20
+ <el-table-column prop="name" label="名称" />
21
+ <el-table-column prop="contact" label="联系人" width="120" />
22
+ </el-table>
23
+ <span slot="footer" class="dialog-footer">
24
+ <el-button type="primary" plain class="button-sty" @click="showDialog = false"
25
+ ><i class="el-icon-close el-icon"></i
26
+ >{{ $t2("取 消", "system.button.cancel2") }}</el-button
27
+ >
28
+ <el-button
29
+ type="primary"
30
+ class="button-sty"
31
+ :disabled="!currentRow"
32
+ @click="handleConfirm"
33
+ ><i class="el-icon-check el-icon"></i
34
+ >{{ $t2("确 定", "system.button.confirm2") }}</el-button
35
+ >
36
+ </span>
37
+ </el-dialog>
38
+ </template>
39
+
40
+ <script>
41
+ export default {
42
+ name: "demoPickerDialog",
43
+ props: ["visiable"],
44
+ data() {
45
+ return {
46
+ showDialog: true,
47
+ currentRow: null,
48
+ rows: [
49
+ { id: "S001", name: "供应商甲", contact: "张三" },
50
+ { id: "S002", name: "供应商乙", contact: "李四" },
51
+ { id: "S003", name: "供应商丙", contact: "王五" },
52
+ ],
53
+ };
54
+ },
55
+ methods: {
56
+ handleCurrentChange(row) {
57
+ this.currentRow = row;
58
+ },
59
+ handleConfirm() {
60
+ if (!this.currentRow) return;
61
+ // 与 formDialog searchComponent 的约定:confirm 事件带选中行数组
62
+ this.$emit("confirm", [this.currentRow]);
63
+ this.showDialog = false;
64
+ },
65
+ handleClose() {
66
+ this.$emit("update:visiable", false);
67
+ },
68
+ },
69
+ };
70
+ </script>
71
+
72
+ <style></style>
@@ -0,0 +1,537 @@
1
+ <template>
2
+ <div class="app-container form-dialog-demo">
3
+ <el-card shadow="never">
4
+ <div slot="header">$formDialog 表单弹框示例</div>
5
+ <div class="btns">
6
+ <el-button type="primary" @click="openBasic">1. 基础新增表单</el-button>
7
+ <el-button type="primary" @click="openEdit">2. 编辑回显</el-button>
8
+ <el-button type="primary" @click="openReadonly">3. 只读查看</el-button>
9
+ <el-button type="primary" @click="openAllTypes"
10
+ >4. 全字段类型(两列布局)</el-button
11
+ >
12
+ <el-button type="primary" @click="openBeforeConfirm"
13
+ >5. beforeConfirm 异步保存</el-button
14
+ >
15
+ <el-button type="primary" @click="openCustomRender"
16
+ >6. 自定义 render 控件</el-button
17
+ >
18
+ <el-button type="primary" @click="openNested"
19
+ >7. 叠开两层弹框</el-button
20
+ >
21
+ <el-button type="primary" @click="openDataSource"
22
+ >8. 搜索框 + 下拉数据源(静态/词汇/脚本)</el-button
23
+ >
24
+ <el-button type="primary" @click="openLinkage"
25
+ >9. 字段联动(级联/显隐/禁用/onChange)</el-button
26
+ >
27
+ <el-button type="primary" @click="openXform"
28
+ >10. xform 整表模式(formCode)</el-button
29
+ >
30
+ <el-button type="primary" @click="openXformReadonly"
31
+ >11. xform 整表只读查看</el-button
32
+ >
33
+ </div>
34
+ </el-card>
35
+
36
+ <el-card shadow="never" style="margin-top: 16px">
37
+ <div slot="header">最近一次返回结果(取消/关闭为 null)</div>
38
+ <pre class="result">{{ result }}</pre>
39
+ </el-card>
40
+ </div>
41
+ </template>
42
+
43
+ <script>
44
+ export default {
45
+ name: "formDialogDemo",
46
+ data() {
47
+ return {
48
+ result: "(还未调用)",
49
+ };
50
+ },
51
+ methods: {
52
+ show(data) {
53
+ this.result = data === null ? "null" : JSON.stringify(data, null, 2);
54
+ },
55
+
56
+ // 1. 基础新增:必填校验 + 常用控件
57
+ async openBasic() {
58
+ const data = await this.$formDialog({
59
+ title: "新增分类",
60
+ fields: [
61
+ { prop: "name", label: "名称", required: true },
62
+ {
63
+ prop: "type",
64
+ label: "类型",
65
+ type: "select",
66
+ required: true,
67
+ options: [
68
+ { label: "普通", value: 1 },
69
+ { label: "重要", value: 2 },
70
+ ],
71
+ },
72
+ { prop: "enabled", label: "启用", type: "switch" },
73
+ { prop: "remark", label: "备注", type: "textarea" },
74
+ ],
75
+ });
76
+ this.show(data);
77
+ },
78
+
79
+ // 2. 编辑回显:model 传初始值
80
+ async openEdit() {
81
+ const data = await this.$formDialog({
82
+ title: "编辑分类",
83
+ model: {
84
+ name: "季度促销",
85
+ type: 2,
86
+ enabled: true,
87
+ remark: "从列表行数据带入的初始值",
88
+ },
89
+ fields: [
90
+ { prop: "name", label: "名称", required: true },
91
+ {
92
+ prop: "type",
93
+ label: "类型",
94
+ type: "select",
95
+ options: [
96
+ { label: "普通", value: 1 },
97
+ { label: "重要", value: 2 },
98
+ ],
99
+ },
100
+ { prop: "enabled", label: "启用", type: "switch" },
101
+ { prop: "remark", label: "备注", type: "textarea" },
102
+ ],
103
+ });
104
+ this.show(data);
105
+ },
106
+
107
+ // 3. 只读查看:readonly=true,整表单禁用,底部只有关闭
108
+ async openReadonly() {
109
+ const data = await this.$formDialog({
110
+ title: "查看分类",
111
+ readonly: true,
112
+ model: { name: "季度促销", type: 2, enabled: true, remark: "只读模式" },
113
+ fields: [
114
+ { prop: "name", label: "名称" },
115
+ {
116
+ prop: "type",
117
+ label: "类型",
118
+ type: "select",
119
+ options: [
120
+ { label: "普通", value: 1 },
121
+ { label: "重要", value: 2 },
122
+ ],
123
+ },
124
+ { prop: "enabled", label: "启用", type: "switch" },
125
+ { prop: "remark", label: "备注", type: "textarea" },
126
+ ],
127
+ });
128
+ this.show(data);
129
+ },
130
+
131
+ // 4. 全字段类型 + span 两列布局 + 自定义 rules + 控件透传 props
132
+ async openAllTypes() {
133
+ const data = await this.$formDialog({
134
+ title: "全字段类型",
135
+ width: "760px",
136
+ rules: {
137
+ qty: [
138
+ {
139
+ validator: (rule, value, callback) => {
140
+ if (value != null && value % 2 !== 0) {
141
+ callback(new Error("数量必须是偶数(自定义 rules 示例)"));
142
+ } else {
143
+ callback();
144
+ }
145
+ },
146
+ trigger: "change",
147
+ },
148
+ ],
149
+ },
150
+ fields: [
151
+ { prop: "name", label: "文本", span: 12, required: true },
152
+ {
153
+ // 数字框走 baseInputNumber:scale 限小数位(0=整数),negative 允许负数
154
+ prop: "qty",
155
+ label: "数量",
156
+ type: "number",
157
+ span: 12,
158
+ scale: 0,
159
+ props: { min: 0, max: 100 },
160
+ },
161
+ {
162
+ prop: "amount",
163
+ label: "金额",
164
+ type: "number",
165
+ span: 12,
166
+ scale: 2,
167
+ negative: true,
168
+ },
169
+ { prop: "date", label: "日期", type: "date", span: 12 },
170
+ { prop: "time", label: "日期时间", type: "datetime", span: 12 },
171
+ { prop: "range", label: "日期区间", type: "daterange", span: 24 },
172
+ {
173
+ prop: "level",
174
+ label: "单选",
175
+ type: "radio",
176
+ span: 12,
177
+ options: [
178
+ { label: "低", value: "L" },
179
+ { label: "中", value: "M" },
180
+ { label: "高", value: "H" },
181
+ ],
182
+ },
183
+ {
184
+ prop: "tags",
185
+ label: "多选",
186
+ type: "checkbox",
187
+ span: 12,
188
+ options: ["标签A", "标签B", "标签C"],
189
+ },
190
+ { prop: "enabled", label: "开关", type: "switch", span: 12 },
191
+ {
192
+ prop: "locked",
193
+ label: "禁用控件",
194
+ type: "switch",
195
+ span: 12,
196
+ disabled: true,
197
+ },
198
+ ],
199
+ });
200
+ this.show(data);
201
+ },
202
+
203
+ // 5. beforeConfirm:确认前调接口,失败不关闭弹框
204
+ async openBeforeConfirm() {
205
+ const data = await this.$formDialog({
206
+ title: "异步保存(勾选“模拟失败”体验保存失败不关闭)",
207
+ fields: [
208
+ { prop: "name", label: "名称", required: true },
209
+ { prop: "mockFail", label: "模拟失败", type: "switch" },
210
+ ],
211
+ beforeConfirm: (formData) =>
212
+ new Promise((resolve, reject) => {
213
+ // 实际业务里这里通常是 this.$http({...}) 保存请求
214
+ setTimeout(() => {
215
+ if (formData.mockFail) {
216
+ this.$baseMessage({
217
+ message: "保存失败,弹框保持打开",
218
+ type: "error",
219
+ });
220
+ reject(new Error("mock fail"));
221
+ } else {
222
+ resolve();
223
+ }
224
+ }, 800);
225
+ }),
226
+ });
227
+ this.show(data);
228
+ },
229
+
230
+ // 6. render 逃生口:内置类型覆盖不了的控件自己渲染
231
+ async openCustomRender() {
232
+ const data = await this.$formDialog({
233
+ title: "自定义 render 控件",
234
+ model: { score: 3, percent: 40 },
235
+ fields: [
236
+ { prop: "name", label: "名称", required: true },
237
+ {
238
+ prop: "score",
239
+ label: "评分",
240
+ render: (h, model) =>
241
+ h("el-rate", {
242
+ props: { value: model.score },
243
+ style: "margin-top:8px",
244
+ on: {
245
+ input: (val) => {
246
+ model.score = val;
247
+ },
248
+ },
249
+ }),
250
+ },
251
+ {
252
+ prop: "percent",
253
+ label: "进度",
254
+ render: (h, model) =>
255
+ h("el-slider", {
256
+ props: { value: model.percent },
257
+ on: {
258
+ input: (val) => {
259
+ model.percent = val;
260
+ },
261
+ },
262
+ }),
263
+ },
264
+ ],
265
+ });
266
+ this.show(data);
267
+ },
268
+
269
+ // 7. 叠开两层:第一层确认后基于结果打开第二层
270
+ async openNested() {
271
+ const first = await this.$formDialog({
272
+ title: "第一层:选择类型",
273
+ fields: [
274
+ {
275
+ prop: "type",
276
+ label: "类型",
277
+ type: "select",
278
+ required: true,
279
+ options: [
280
+ { label: "普通", value: 1 },
281
+ { label: "重要", value: 2 },
282
+ ],
283
+ },
284
+ ],
285
+ });
286
+ if (first === null) {
287
+ this.show(null);
288
+ return;
289
+ }
290
+ const second = await this.$formDialog({
291
+ title: `第二层:填写${first.type === 2 ? "重要" : "普通"}事项`,
292
+ model: { type: first.type },
293
+ fields: [
294
+ { prop: "name", label: "名称", required: true },
295
+ { prop: "remark", label: "备注", type: "textarea" },
296
+ ],
297
+ });
298
+ this.show(second === null ? null : { ...first, ...second });
299
+ },
300
+
301
+ // 8. 搜索框 + 下拉数据源:静态 options / 词汇 dictCode / 脚本 scriptCode
302
+ async openDataSource() {
303
+ const customers = [
304
+ { id: 1001, name: "华南贸易有限公司" },
305
+ { id: 1002, name: "北方物流集团" },
306
+ { id: 1003, name: "东部电子科技" },
307
+ ];
308
+ const data = await this.$formDialog({
309
+ title: "搜索框与下拉数据源",
310
+ labelWidth: "120px",
311
+ fields: [
312
+ {
313
+ prop: "staticType",
314
+ label: "静态下拉",
315
+ type: "select",
316
+ options: [
317
+ { label: "普通", value: 1 },
318
+ { label: "重要", value: 2 },
319
+ ],
320
+ },
321
+ {
322
+ // 词汇下拉:显示 value、取值 sn;dictCode 替换为环境中存在的词汇编码
323
+ prop: "dictType",
324
+ label: "词汇下拉",
325
+ type: "select",
326
+ dictCode: "kehuGrade",
327
+ },
328
+ {
329
+ // 脚本下拉:"formCode/scriptCode",labelField/valueField 按脚本返回结构调整;
330
+ // 也可用 parseOptions: (res) => [{label, value}] 完全自定义解析
331
+ prop: "scriptType",
332
+ label: "脚本下拉",
333
+ type: "select",
334
+ scriptCode: "/intf/listUsers",
335
+ labelField: "nick_name",
336
+ valueField: "id",
337
+ },
338
+ {
339
+ // 搜索方式1:onSearch 完全自定义(这里用嵌套 formDialog 模拟选择)
340
+ prop: "customerName",
341
+ label: "客户(onSearch)",
342
+ type: "search",
343
+ required: true,
344
+ placeholder: "点击搜索按钮选择",
345
+ onSearch: async (model) => {
346
+ const picked = await this.$formDialog({
347
+ title: "选择客户",
348
+ width: "420px",
349
+ fields: [
350
+ {
351
+ prop: "customerId",
352
+ label: "客户",
353
+ type: "select",
354
+ required: true,
355
+ options: customers.map((item) => ({
356
+ label: item.name,
357
+ value: item.id,
358
+ })),
359
+ },
360
+ ],
361
+ });
362
+ if (picked) {
363
+ const row = customers.find(
364
+ (item) => item.id === picked.customerId
365
+ );
366
+ model.customerId = row.id;
367
+ model.customerName = row.name;
368
+ }
369
+ },
370
+ // 清除时把关联的 id 一起清掉
371
+ onClear: (model) => {
372
+ model.customerId = null;
373
+ model.customerName = null;
374
+ },
375
+ },
376
+ {
377
+ // 搜索方式2:searchComponent 打开自定义 Vue 弹框组件,
378
+ // 组件 $emit("confirm", rows) 后按 fieldMap 回写 model
379
+ prop: "supplierName",
380
+ label: "供应商(Vue弹框)",
381
+ type: "search",
382
+ placeholder: "点击搜索按钮选择",
383
+ searchComponent: {
384
+ componentPath: "views/user/user/dialog.vue",
385
+ fieldMap: { supplierId: "id", supplierName: "nickName" },
386
+ },
387
+ onClear: (model) => {
388
+ model.supplierId = null;
389
+ model.supplierName = null;
390
+ },
391
+ },
392
+ {
393
+ // 搜索方式3:searchDialog 打开 xform 搜索弹框(searchFormDialog),
394
+ // formCode 替换为环境中已配置搜索弹框的表单编码
395
+ prop: "billName",
396
+ label: "单据(xform弹框)",
397
+ type: "search",
398
+ placeholder: "点击搜索按钮选择",
399
+ searchDialog: {
400
+ formCode: "user_dialog",
401
+ multiple: false,
402
+ // queryParam: {...}, // 额外查询参数
403
+ fieldMap: { billId: "id", billName: "nick_name" },
404
+ // onConfirm: (model, rows) => {...} // 或完全自定义回写
405
+ },
406
+ onClear: (model) => {
407
+ model.billId = null;
408
+ model.billName = null;
409
+ },
410
+ },
411
+ ],
412
+ });
413
+ this.show(data);
414
+ },
415
+
416
+ // 9. 字段联动:级联下拉(dependsOn) + 显隐(visible) + 禁用(disabled) + onChange
417
+ async openLinkage() {
418
+ const cityMap = {
419
+ gd: [
420
+ { label: "广州", value: "gz" },
421
+ { label: "深圳", value: "sz" },
422
+ ],
423
+ hn: [
424
+ { label: "长沙", value: "cs" },
425
+ { label: "岳阳", value: "yy" },
426
+ ],
427
+ };
428
+ const data = await this.$formDialog({
429
+ title: "字段联动",
430
+ fields: [
431
+ {
432
+ prop: "province",
433
+ label: "省份",
434
+ type: "select",
435
+ required: true,
436
+ options: [
437
+ { label: "广东", value: "gd" },
438
+ { label: "湖南", value: "hn" },
439
+ ],
440
+ // onChange(value, model, field, dialog):dialog 上有
441
+ // setFieldOptions / reloadFieldOptions 可手动控制其他字段选项
442
+ onChange: (val, model) => {
443
+ model.provinceName = { gd: "广东", hn: "湖南" }[val] || null;
444
+ },
445
+ },
446
+ {
447
+ // 级联:province 变化时自动清空 city;选项用函数按 model 动态取。
448
+ // 若选项来自 dictCode/scriptCode,dependsOn 还会自动重新拉取
449
+ //(dictData/scriptData 支持函数形式 (model) => ({...}))
450
+ prop: "city",
451
+ label: "城市",
452
+ type: "select",
453
+ required: true,
454
+ dependsOn: ["province"],
455
+ options: (model) => cityMap[model.province] || [],
456
+ },
457
+ { prop: "needInvoice", label: "需要发票", type: "switch" },
458
+ {
459
+ // 显隐联动:开关打开才显示;隐藏时不渲染也不参与必填校验
460
+ prop: "taxNo",
461
+ label: "税号",
462
+ required: true,
463
+ visible: (model) => model.needInvoice === true,
464
+ },
465
+ {
466
+ prop: "level",
467
+ label: "等级",
468
+ type: "radio",
469
+ options: [
470
+ { label: "普通", value: 1 },
471
+ { label: "特殊", value: 2 },
472
+ ],
473
+ },
474
+ {
475
+ // 禁用联动:等级为"特殊"才允许填原因
476
+ prop: "reason",
477
+ label: "特殊原因",
478
+ type: "textarea",
479
+ disabled: (model) => model.level !== 2,
480
+ },
481
+ ],
482
+ });
483
+ this.show(data);
484
+ },
485
+
486
+ // 10. xform 整表模式:formCode 渲染设计器里配置好的表单,确认走 xform 校验
487
+ // formCode 为占位编码,按环境替换为已发布的表单模板编码
488
+ async openXform() {
489
+ const data = await this.$formDialog({
490
+ formCode: "demoForm",
491
+ // title 不传时默认取表单模板名称;width 默认 1000px
492
+ // model: { name: "初始值" }, // 初始数据(结构与 getFormData 一致)
493
+ // optionData / globalDsv 可透传给 VFormRender
494
+ // onReady: (formRef, dialog) => {}, // 表单挂载后回调,可调 setFieldValue 等
495
+ beforeConfirm: (formData) => {
496
+ // 常规用法:这里调保存接口,reject 则弹框不关闭
497
+ console.log("xform 表单数据", formData);
498
+ },
499
+ });
500
+ this.show(data);
501
+ },
502
+
503
+ // 11. xform 整表只读:readonly=true 走 setReadMode,底部只有关闭
504
+ async openXformReadonly() {
505
+ const data = await this.$formDialog({
506
+ formCode: "demoForm",
507
+ readonly: true,
508
+ model: {},
509
+ });
510
+ this.show(data);
511
+ },
512
+ },
513
+ };
514
+ </script>
515
+
516
+ <style lang="scss" scoped>
517
+ .form-dialog-demo {
518
+ padding: 16px;
519
+
520
+ .btns {
521
+ display: flex;
522
+ flex-wrap: wrap;
523
+ gap: 8px;
524
+
525
+ .el-button {
526
+ margin-left: 0;
527
+ }
528
+ }
529
+
530
+ .result {
531
+ margin: 0;
532
+ min-height: 60px;
533
+ white-space: pre-wrap;
534
+ word-break: break-all;
535
+ }
536
+ }
537
+ </style>
@@ -280,6 +280,10 @@ export default {
280
280
  },
281
281
  beforeDestroy() {
282
282
  this.clearTimer();
283
+ if (this.reloadHomeMenuHandler) {
284
+ this.$baseEventBus.$off("reloadHomeMenu", this.reloadHomeMenuHandler);
285
+ this.reloadHomeMenuHandler = null;
286
+ }
283
287
  },
284
288
  created() {
285
289
  this.getUserInfo();
@@ -520,9 +524,12 @@ export default {
520
524
  },
521
525
 
522
526
  initMenu() {
523
- this.$baseEventBus.$on("reloadHomeMenu", (menuId) => {
527
+ // handler 存实例属性并在销毁时 $off:home 每次登出/登录重建都会重新注册,
528
+ // 不解绑会累积 handler 并钉住已销毁的 home 实例(内存泄露)
529
+ this.reloadHomeMenuHandler = (menuId) => {
524
530
  this.$forceUpdate();
525
- });
531
+ };
532
+ this.$baseEventBus.$on("reloadHomeMenu", this.reloadHomeMenuHandler);
526
533
  },
527
534
  filterRoute() {
528
535
  let oldRoutes = JSON.parse(JSON.stringify(this.sidebarRouters));