cloud-web-corejs 1.0.54-dev.786 → 1.0.54-dev.788
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 +2 -1
- package/src/components/VabUpload/index.vue +1 -1
- package/src/components/baseInputExport/mixins.js +13 -2
- package/src/components/code-editor/async.js +29 -0
- package/src/components/code-editor/index.vue +65 -6
- package/src/components/excelImport/index.js +69 -7
- package/src/components/excelImport/mixins.js +841 -9
- package/src/components/fileLibrary/fileHistoryDialog.vue +1 -1
- package/src/components/fileLibrary/fileObjAuthEdit.vue +1 -1
- package/src/components/fileLibrary/filterDialog.vue +1 -1
- package/src/components/formDialog/README.md +324 -241
- package/src/components/formDialog/actionButtons.js +90 -0
- package/src/components/formDialog/index.js +48 -35
- package/src/components/formDialog/index.vue +802 -724
- package/src/components/formDrawer/drawer.vue +719 -0
- package/src/components/formDrawer/index.js +50 -0
- package/src/components/onlineTalk/talkUserDialog.vue +1 -1
- package/src/components/table/index.js +4 -0
- package/src/components/tempStorage/tempStorageDialog.vue +1 -1
- package/src/components/wf/addTaskUserdialog.vue +1 -1
- package/src/components/xform/docs/2026-08 /345/210/227/350/241/250/345/257/274/345/207/272/350/204/261/346/225/217/345/210/227/345/211/224/351/231/244/350/220/275/345/234/260/346/226/207/346/241/243.md" +165 -0
- package/src/components/xform/form-designer/form-widget/container-widget/data-table-widget.vue +1 -1
- package/src/components/xform/form-designer/form-widget/dialog/formDialog.vue +115 -4
- package/src/components/xform/form-designer/form-widget/dialog/formDrawer.vue +102 -5
- package/src/components/xform/form-designer/form-widget/dialog/formFieldDialog.vue +1 -1
- package/src/components/xform/form-designer/form-widget/dialog/vabSearchDialog.vue +1 -1
- package/src/components/xform/form-designer/form-widget/field-widget/form-item-wrapper.vue +18 -73
- package/src/components/xform/form-designer/form-widget/field-widget/script-input-widget.vue +3 -0
- package/src/components/xform/form-designer/setting-panel/action-button-config-dialog.vue +241 -0
- package/src/components/xform/form-designer/setting-panel/form-setting.vue +116 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/container-detail/detail-editor.vue +6 -0
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +1 -0
- package/src/components/xform/form-render/container-item/data-table-mixin.js +1480 -1460
- package/src/components/xform/form-render/container-item/detail-item.vue +11 -2
- package/src/components/xform/form-render/container-item/list-h5-item.vue +12 -0
- package/src/components/xform/form-render/indexMixin.js +5 -0
- package/src/components/xform/utils/textMaskUtil.js +186 -0
- package/src/index.js +505 -472
- package/src/store/config/index.js +224 -0
- package/src/store/modules/tagsView.js +4 -2
- package/src/utils/lazyPlugin.js +66 -0
- package/src/views/bd/project/branch/bd_branch/edit.vue +145 -0
- package/src/views/bd/project/branch/bd_branch/list.vue +72 -0
- package/src/views/bd/project/branch/bd_branch/menuKindDialog.vue +106 -0
- package/src/views/bd/project/branch/bd_branch/mixins/edit.js +189 -0
- package/src/views/bd/project/branch/bd_branch/mixins/list.js +124 -0
- package/src/views/bd/project/branch/bd_branch/mixins/menuKindDialog.js +85 -0
- package/src/views/bd/project/branch/form_script/list.vue +22 -0
- package/src/views/bd/project/branch/form_template/list.vue +21 -0
- package/src/views/bd/project/branch/menu_kind/list.vue +21 -0
- package/src/views/bd/project/common/form_script/list.vue +16 -0
- package/src/views/bd/project/common/form_template/list.vue +15 -0
- package/src/views/bd/project/common/menu_kind/list.vue +15 -0
- package/src/views/bd/project/core/branchContext.js +100 -0
- package/src/views/bd/project/core/branchPage.js +78 -0
- package/src/views/bd/project/core/branchPageWrap.vue +216 -0
- package/src/views/bd/project/core/branchScope.js +44 -0
- package/src/views/bd/project/core/branchSelect.vue +106 -0
- package/src/views/bd/project/core/httpBranchPatch.js +58 -0
- package/src/views/bd/project/datatable/menu_kind/list.vue +15 -0
- package/src/views/bd/project/datatable/table_model/list.vue +15 -0
- package/src/views/bd/setting/bd_attach_setting/dialog.vue +1 -1
- package/src/views/bd/setting/bd_attach_setting/list.vue +1 -1
- package/src/views/bd/setting/bd_company_env/dialog.vue +1 -1
- package/src/views/bd/setting/bd_company_env/list.vue +1 -1
- package/src/views/bd/setting/formVersion/ftHistoryDialog.vue +1 -1
- package/src/views/bd/setting/form_import_log/list.vue +1 -1
- package/src/views/bd/setting/form_script/dialog.vue +1 -1
- package/src/views/bd/setting/form_script/form_list.vue +1 -1
- package/src/views/bd/setting/form_script/list.vue +1 -1
- package/src/views/bd/setting/form_script/list1.vue +1 -1
- package/src/views/bd/setting/form_template/dialog.vue +1 -1
- package/src/views/bd/setting/form_template/formDesignerDialog.vue +1 -0
- package/src/views/bd/setting/form_template/ftHistoryDialog.vue +1 -1
- package/src/views/bd/setting/form_template/itemList.vue +1 -1
- package/src/views/bd/setting/form_template/list.vue +3 -3
- package/src/views/bd/setting/form_template/mixins/edit.js +3 -3
- package/src/views/bd/setting/form_template/mixins/list.js +3 -3
- package/src/views/bd/setting/form_template/mixins/list2.js +3 -3
- package/src/views/bd/setting/form_template/mixins/wf_list.js +3 -3
- package/src/views/bd/setting/form_template/wf_list.vue +3 -3
- package/src/views/bd/setting/logic_param/list.vue +1 -1
- package/src/views/bd/setting/menu_kind/dialog.vue +1 -1
- package/src/views/bd/setting/menu_kind/list.vue +1 -1
- package/src/views/bd/setting/push_data/list.vue +1 -1
- package/src/views/bd/setting/push_data_h/list.vue +1 -1
- package/src/views/bd/setting/request_async_setting/list.vue +1 -1
- package/src/views/bd/setting/request_setting/list.vue +1 -1
- package/src/views/bd/setting/table_model/dialog.vue +1 -1
- package/src/views/bd/setting/table_model/list.vue +1 -1
- package/src/views/bd/setting/table_model/zdDialog.vue +1 -1
- package/src/views/support/export_template/list.vue +1 -1
- package/src/views/user/access_log/list.vue +1 -1
- package/src/views/user/access_log/list2.vue +1 -1
- package/src/views/user/api_request/list.vue +1 -1
- package/src/views/user/area/dialog.vue +1 -1
- package/src/views/user/area/list.vue +1 -1
- package/src/views/user/bill_setting/list.vue +1 -1
- package/src/views/user/bill_setting/userDialog.vue +1 -1
- package/src/views/user/code_rules/list.vue +1 -1
- package/src/views/user/common_attribute/list.vue +1 -1
- package/src/views/user/common_script/list.vue +1 -1
- package/src/views/user/company_info/dialog.vue +1 -1
- package/src/views/user/country/dialog.vue +1 -1
- package/src/views/user/country/list.vue +1 -1
- package/src/views/user/data_type_setting/dialog.vue +1 -1
- package/src/views/user/extend_datasource/dialog.vue +1 -1
- package/src/views/user/extend_datasource/list.vue +1 -1
- package/src/views/user/fieldTranslation/list.vue +1 -1
- package/src/views/user/field_values_invisible/list.vue +1 -1
- package/src/views/user/file_type/list.vue +1 -1
- package/src/views/user/file_view_area/list.vue +1 -1
- package/src/views/user/form/form_template/dialog.vue +1 -1
- package/src/views/user/form/form_template/itemList.vue +1 -1
- package/src/views/user/form/form_template/list.vue +1 -1
- package/src/views/user/form/form_template_field/dialog.vue +1 -1
- package/src/views/user/form/form_type/dialog.vue +1 -1
- package/src/views/user/form/form_type/list.vue +1 -1
- package/src/views/user/form/report_requestaccess/dialog.vue +1 -1
- package/src/views/user/form/report_requestaccess/list.vue +1 -1
- package/src/views/user/form/vform/designer.vue +51 -22
- package/src/views/user/groups/dialog.vue +1 -1
- package/src/views/user/groups/list.vue +1 -1
- package/src/views/user/lang_tag/dialog.vue +1 -1
- package/src/views/user/lang_tag/list.vue +1 -1
- package/src/views/user/language_setting/list.vue +1 -1
- package/src/views/user/menu/list.vue +1 -1
- package/src/views/user/mobile_menu/list.vue +1 -1
- package/src/views/user/notify_message/list.vue +1 -1
- package/src/views/user/notify_template/list.vue +1 -1
- package/src/views/user/notify_template/list2.vue +1 -1
- package/src/views/user/oplog/list.vue +1 -1
- package/src/views/user/position/dialog.vue +1 -1
- package/src/views/user/position/list.vue +1 -1
- package/src/views/user/project_tag/dialog.vue +1 -1
- package/src/views/user/project_tag/list.vue +1 -1
- package/src/views/user/push_setting/list.vue +1 -1
- package/src/views/user/request_setting/list.vue +1 -1
- package/src/views/user/role/dialog.vue +1 -1
- package/src/views/user/role/list.vue +1 -1
- package/src/views/user/sale_org/dialog.vue +1 -1
- package/src/views/user/sale_org/list.vue +1 -1
- package/src/views/user/system_notice/list.vue +1 -1
- package/src/views/user/system_parameter/list.vue +1 -1
- package/src/views/user/user/dialog.vue +1 -1
- package/src/views/user/user/form_dialog.vue +1 -1
- package/src/views/user/user/form_list.vue +1 -1
- package/src/views/user/user/list.vue +2 -2
- package/src/views/user/user_log_classify/list.vue +1 -1
- package/src/views/user/wf/wfReport/index.vue +2 -2
- package/src/views/user/wf/wf_auto_submit_data/list.vue +2 -2
- package/src/views/user/wf/wf_manage/list.vue +2 -2
- package/src/views/user/wf/wf_manage/list2.vue +2 -2
- package/src/views/user/wf/wf_obj_config/dialog.vue +1 -1
- package/src/views/user/wf/wf_obj_config/list.vue +1 -1
- package/src/views/user/wf/wf_obj_config/list_form.vue +1 -1
- package/src/views/user/wf/wf_transfer_setting/list.vue +1 -1
- package/src/views/user/workbench_menu/list.vue +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const modules = {};
|
|
2
|
+
import vue from "vue";
|
|
3
|
+
|
|
4
|
+
// 壳组件懒加载:首屏只留本入口几十行;抽屉壳(含 select 逻辑、按钮公共模块)
|
|
5
|
+
// 随首次调用进独立 chunk,xform 渲染器与 formDialog 共享同一异步 chunk
|
|
6
|
+
let FormDrawerCtor = null;
|
|
7
|
+
|
|
8
|
+
function loadCtor() {
|
|
9
|
+
if (FormDrawerCtor) return Promise.resolve(FormDrawerCtor);
|
|
10
|
+
return import(
|
|
11
|
+
/* webpackChunkName: "form-drawer" */ "./drawer.vue"
|
|
12
|
+
).then((m) => {
|
|
13
|
+
FormDrawerCtor = vue.extend(m.default);
|
|
14
|
+
return FormDrawerCtor;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* JS 调用打开 formCode 抽屉。每次调用创建独立实例,支持叠开多层。
|
|
20
|
+
* options:{ formCode, title, size, direction, param, conditionParam, plain,
|
|
21
|
+
* select: {multiple, rows, fieldKey, queryParam, onConfirm},
|
|
22
|
+
* buttons, showFooter, readonly, onReady, beforeConfirm, model, ... }
|
|
23
|
+
* resolve:默认按钮下 select 模式返回选中行数组、表单模式返回表单数据;
|
|
24
|
+
* 配置了自定义按钮(formConfig.actionButtons / options.buttons)返回 {action, data};
|
|
25
|
+
* 取消/关闭返回 null。详见 components/formDialog/README.md。
|
|
26
|
+
*/
|
|
27
|
+
function open(options = {}) {
|
|
28
|
+
return loadCtor().then((Ctor) => {
|
|
29
|
+
const parent
|
|
30
|
+
= typeof window !== "undefined" && window.$vueRoot ? window.$vueRoot : null;
|
|
31
|
+
const instanceOptions = {};
|
|
32
|
+
if (parent) {
|
|
33
|
+
instanceOptions.parent = parent;
|
|
34
|
+
if (parent._i18n) {
|
|
35
|
+
instanceOptions.i18n = parent._i18n;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const instance = new Ctor(instanceOptions);
|
|
39
|
+
instance.$mount();
|
|
40
|
+
document.body.appendChild(instance.$el);
|
|
41
|
+
return instance.open(options);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
modules.install = function (Vue) {
|
|
46
|
+
Vue.prototype.$formDrawer = open;
|
|
47
|
+
};
|
|
48
|
+
modules.open = open;
|
|
49
|
+
|
|
50
|
+
export default modules;
|
|
@@ -576,6 +576,10 @@ function initOption(opts) {
|
|
|
576
576
|
types: ["xlsx", "csv", "html", "xml", "txt"],
|
|
577
577
|
// 仅保留当前页:mode=all 依赖的 queryAll 无鉴权实现,从未可用;全量导出请走 $excelExport
|
|
578
578
|
modes: ["current"],
|
|
579
|
+
// 与 $excelExport 一致地剔除非数据列与脱敏列,避免绕开 $excelExport 导出明文
|
|
580
|
+
columnFilterMethod({ column }) {
|
|
581
|
+
return !column.params?.exportDisabled;
|
|
582
|
+
},
|
|
579
583
|
},
|
|
580
584
|
radioConfig: {
|
|
581
585
|
labelField: "id",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<div class="clearfix screen-btns">
|
|
34
34
|
<div class="fr">
|
|
35
35
|
<vxe-button icon="el-icon-brush" class="button-sty" @click="resetEvent" type="text" status="primary"
|
|
36
|
-
plain>{{ $
|
|
36
|
+
plain>{{ $t1('刷新') }}
|
|
37
37
|
</vxe-button>
|
|
38
38
|
<vxe-button status="warning" icon="el-icon-search" class="button-sty" @click="searchEvent">
|
|
39
39
|
{{ $t2('搜索', 'system.button.search') }}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# 列表导出脱敏列剔除(2026-08)
|
|
2
|
+
|
|
3
|
+
> 起因:配置了「加密」(密文文本)的字段,页面显示 `138****1234`,导出 Excel 却是完整明文。
|
|
4
|
+
> 排查范围:`src/components/xform/**`、`src/components/excelExport/**`、`src/components/table/**`。
|
|
5
|
+
|
|
6
|
+
## 一、问题定性
|
|
7
|
+
|
|
8
|
+
「加密」是**纯前端展示遮罩**,不是数据加密:
|
|
9
|
+
|
|
10
|
+
- 配置入口:属性面板 `textFlag-editor.vue` → `options.userTextRuleEnabled` + `options.userTextRuleConfig[]`
|
|
11
|
+
- 单条规则 `{ serveName, companyCodes, roleCodes, type, encryptFormula }`,`type=1` 角色不匹配时脱敏、`type=2` 角色匹配时脱敏,`encryptFormula` 形如 `3*3`
|
|
12
|
+
- 改造前实现只有一处:`form-item-wrapper.vue` —— `created` → `initShowType()` 算出 `widgetTextFlag=2`,模板改渲染 `<span>{{ getShowValue() }}</span>`,`getShowValue()` → `getProcessText()` 做星号替换
|
|
13
|
+
|
|
14
|
+
**原始值始终完整存在于 `row[field]` / `fieldModel` 中**,遮罩只发生在渲染层。
|
|
15
|
+
|
|
16
|
+
## 二、根因:导出取值绕开渲染层
|
|
17
|
+
|
|
18
|
+
导出取值优先级(`excelExport/mixins.js:828 getExportCellValue`):
|
|
19
|
+
|
|
20
|
+
1. `column.params.exportVal`
|
|
21
|
+
2. **`column.slots.filterVal(params)`** ← 命中点
|
|
22
|
+
3. 无自定义渲染 → `row[field]`(有 `formatter` 则格式化)
|
|
23
|
+
4. 兜底 `getCellValue()`:挂载 vNode 渲染再爬 DOM
|
|
24
|
+
|
|
25
|
+
而 `data-table-mixin.js:1888` 对 `input / number / text / select / checkbox / radio / status / vabsearch / date / textarea / a-link / a-text` 这 12 类组件列统一注册了 `col.slots.filterVal`,函数体直接返回 `row[field]`(select/radio/checkbox 返回 optionLabel,vabsearch 返回 label 字段,date 返回格式化值)——**整条路径不碰 `form-item-wrapper`,不读 `widgetTextFlag`**。
|
|
26
|
+
|
|
27
|
+
这 12 类恰好是最常配脱敏的字段类型。副作用是不一致:不在这 12 类里的组件列(`slots.default = "widget"`,无 `filterVal`)走第 4 条兜底,会真实渲染 wrapper 并爬 DOM,**导出的反而是星号**。同一张表有的列明文、有的列打码,且非有意设计。
|
|
28
|
+
|
|
29
|
+
其它同源泄漏点:
|
|
30
|
+
|
|
31
|
+
| 路径 | 改造前 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| 明细导出(`exportItemColumns`,`data-table-mixin.js:2406`) | 必明文:列只有 `field/title`,行数据后端原样返回 |
|
|
34
|
+
| 条件导出 / 全量导出 | 明文:`loopToDo` 分页拉后端原始行 → 同一个 `getExcelData` |
|
|
35
|
+
| H5 列表容器 `list-h5-item.vue` | 明文:独立 `exportData`,列带 `params.widget` 但无 `filterVal` |
|
|
36
|
+
| vxe 工具栏自带导出(`table/index.js` `exportConfig`) | 明文:vxe 用行原始值 |
|
|
37
|
+
| 列筛选下拉候选值(`vxeFilter/mixin.js`) | 明文(不属导出,本轮未处理) |
|
|
38
|
+
| 接口响应 / JS 内存 | 明文(需后端脱敏,前端不可解) |
|
|
39
|
+
|
|
40
|
+
## 三、方案选型
|
|
41
|
+
|
|
42
|
+
| 档位 | 做法 | 结论 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| 1 | 前端一致性:给 `filterVal` 结果也套一层遮罩 | 未选。要同步改 4 处取值路径,且"导出打码值"对下游数据消费无意义 |
|
|
45
|
+
| **2** | **命中脱敏规则的列直接整列不导出** | **本次采用** |
|
|
46
|
+
| 3 | 后端按角色脱敏返回 / 提供脱敏导出接口 | 真正解法,本轮范围外。方案 1、2 都只是"看不见",不是"拿不到" |
|
|
47
|
+
|
|
48
|
+
方案 2 之所以成立:**所有导出(列表 / 条件 / 选择 / 明细)都必须先过 `exportFieldDialog`**(`mixins.js:133` 是唯一入口,`:1072` 回传 columns/leafColumns),而 `params.exportDisabled` 已被三处消费:
|
|
49
|
+
|
|
50
|
+
| 位置 | 效果 |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `exportFieldDialog.vue:95 filterNonDataExportColumns`(`:160` 调用) | 字段选择树里不出现该列,子列全没了则父分组也去掉 |
|
|
53
|
+
| `exportFieldDialog.vue:319 dialogConfirm→loopDo` | 生成表头 JSON 时跳过 |
|
|
54
|
+
| `excelExport/mixins.js:860 getExcelData` | 取数时过滤 `leafColumns` |
|
|
55
|
+
|
|
56
|
+
所以只要把标记打到列上即可一次收口,无需改任何取值逻辑。
|
|
57
|
+
|
|
58
|
+
> 曾评估过变体「保留表头、值置空」:需给通用 `excelExport` 新增 `exportMasked` 语义,且整列空白易被下游误读成"该记录无值",已否决。
|
|
59
|
+
|
|
60
|
+
## 四、改动清单
|
|
61
|
+
|
|
62
|
+
### 1. 新增 `src/components/xform/utils/textMaskUtil.js`
|
|
63
|
+
|
|
64
|
+
脱敏判定的**唯一实现**,渲染侧与导出侧共用(否则界面打码、导出放行必然漂移)。
|
|
65
|
+
|
|
66
|
+
| 导出 | 职责 |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `resolveTextMaskFlag(widgetOptions, ctx)` | 判定是否脱敏 + 带出命中的 `encryptFormula` |
|
|
69
|
+
| `compareToList(list1, list2)` | 编码列表匹配,空列表表示不限 |
|
|
70
|
+
| `hasTextMaskRule(widgetOptions)` | 是否配了有效规则(不代表当前用户命中) |
|
|
71
|
+
| `buildTextMaskContext(formRef, companyCode)` | 组装 `{ bdService, companyCode, roleCodeList }` |
|
|
72
|
+
| `isTextMaskContextReady(ctx)` | 上下文是否就绪 |
|
|
73
|
+
| `markMaskedExportColumns($grid, ctx)` | 给命中列打 `params.exportDisabled`,并同步到明细导出列 |
|
|
74
|
+
|
|
75
|
+
判定逻辑逐行照搬原 `initShowType`,包括两个易被"优化"掉的细节:
|
|
76
|
+
|
|
77
|
+
- 规则列表**全遍历而非命中即止**,后置规则会覆盖前置结论;
|
|
78
|
+
- `encryptFormula` 只要 serveName/companyCodes 命中就赋值,与 masked 与否无关。
|
|
79
|
+
|
|
80
|
+
### 2. `form-item-wrapper.vue:676 initShowType`
|
|
81
|
+
|
|
82
|
+
保留两个早退外壳(`!!this.designer`、`isLoadEntity && !dataId`)与 `field.options.widgetTextFlag` 写回,中间判定改调 util。行为等价,净减 73 行。
|
|
83
|
+
|
|
84
|
+
`compareToList` 保留为薄封装委托 util —— 本包对外发布,不删以免破坏下游调用。
|
|
85
|
+
|
|
86
|
+
### 3. `data-table-mixin.js`
|
|
87
|
+
|
|
88
|
+
- `:3231 markMaskedColumnsForExport()` —— 3 行委托 util
|
|
89
|
+
- `:3245 exportData()` 入口调用
|
|
90
|
+
- `:253 mounted` 建列后 `$nextTick` 再调一次(见下节决策 3)
|
|
91
|
+
|
|
92
|
+
### 4. `list-h5-item.vue:1664 / :1671`
|
|
93
|
+
|
|
94
|
+
H5 列表容器有独立 `exportData`,同样处理。这是编码阶段才发现的第二条泄漏路径。
|
|
95
|
+
|
|
96
|
+
### 5. `components/table/index.js:580`
|
|
97
|
+
|
|
98
|
+
`exportConfig` 增加 `columnFilterMethod`,堵 vxe 工具栏自带导出旁路。
|
|
99
|
+
|
|
100
|
+
**行为变化**:原有的操作列、图片列等 `exportDisabled` 列也会从原生导出中消失。属修正,但需业务知晓。
|
|
101
|
+
|
|
102
|
+
### 6. `tests/unit/components/xform/textMaskUtil.spec.js`
|
|
103
|
+
|
|
104
|
+
17 个用例,覆盖规则匹配的各分支、fail-closed、自愈还原、分组表头递归、明细列对齐。
|
|
105
|
+
|
|
106
|
+
## 五、关键设计决策
|
|
107
|
+
|
|
108
|
+
**决策 1 —— 判定放在导出触发时,不放在建列时。**
|
|
109
|
+
`bdService` / `userRoleDTOs` 由异步接口填充(`form-render/indexMixin.js:458、479、498`),建列时可能尚未就绪。
|
|
110
|
+
|
|
111
|
+
**决策 2 —— 上下文未就绪时 fail-closed。**
|
|
112
|
+
`compareToList` 在角色列表为空时返回 false → 被判成"角色不匹配":对 `type=1` 规则偏保守(判为脱敏,安全),但对 `type=2` 规则会判成**不脱敏**,正好是危险方向。故上下文未就绪时,凡 `hasTextMaskRule` 为真的列一律剔除。
|
|
113
|
+
|
|
114
|
+
**决策 3 —— `mounted` 也标记一次。**
|
|
115
|
+
vxe 原生导出不走 `exportData()`,若只在导出入口标记,`columnFilterMethod` 会拿到一张没标记的列表,等于没堵。建列后立刻按 fail-closed 打标,之后每次 `exportData` 自愈修正。用户先点 $excelExport 再点原生导出的顺序也天然安全。
|
|
116
|
+
|
|
117
|
+
**决策 4 —— 标记幂等且可自愈,用私有键区分归属。**
|
|
118
|
+
`exportDisabled` 也可能是 `isNonDataExportColumn`(`data-table-mixin.js:1710`)打的。若无差别地 `delete`,自愈会误放行操作列。故引入 `MASK_MARK_KEY = "exportDisabledByTextMask"`(`textMaskUtil.js:19`):
|
|
119
|
+
|
|
120
|
+
- 列本就带 `exportDisabled` 且无私有键 → 不接管,不打私有键
|
|
121
|
+
- 由本函数从无到有打上 → 同时写私有键
|
|
122
|
+
- 撤销时只在私有键存在时才删
|
|
123
|
+
|
|
124
|
+
## 六、覆盖范围
|
|
125
|
+
|
|
126
|
+
| 泄漏点 | 现状 |
|
|
127
|
+
|---|---|
|
|
128
|
+
| 列表导出 / 条件导出 / 选择导出 | ✅ 堵住 |
|
|
129
|
+
| H5 列表导出 | ✅ 堵住 |
|
|
130
|
+
| 明细导出 | ⚠️ 按 field 名对齐,见局限 L1 |
|
|
131
|
+
| vxe 工具栏原生导出 | ✅ 决策 3 + 改动 5 |
|
|
132
|
+
| 列筛选下拉候选值明文 | ❌ 未纳入(不属导出) |
|
|
133
|
+
| 接口响应 / JS 内存明文 | ❌ 需后端脱敏 |
|
|
134
|
+
| 打印 / PDF | 走 DOM,应是打码的,**未验证** |
|
|
135
|
+
| 单元格区域复制 | **未验证**,疑似明文 |
|
|
136
|
+
|
|
137
|
+
## 七、已知局限
|
|
138
|
+
|
|
139
|
+
- **L1**:明细导出列只有 `field/title`,只能按字段名匹配主表脱敏字段集合。明细独有字段、或明细导出脚本返回的字段名与主表不一致时,仍会明文导出。
|
|
140
|
+
- **L2**:动态列 / 行级 schema(`fieldSchemaMap`)下同列不同行配置可能不同,判定按列模板走;模板未配、个别行配了的情况会漏。
|
|
141
|
+
- **L3**:表单低代码脚本里直接读 `row.xxx`、或列上配了自定义 `params.exportVal` 的,不受影响。
|
|
142
|
+
- **L4**:脱敏是**列级**判定;将来若出现行级脱敏,本方案不适用。
|
|
143
|
+
- **L5**:`exportFieldDialog.addTableExportColumns()`(`:252`)会把勾选状态持久化到 `$grid.exportColumns`。脱敏列不在树里 → 记为 `export:false`。同一用户换成不脱敏角色后,该列默认不勾选,需手动勾一次。
|
|
144
|
+
|
|
145
|
+
## 八、验证情况
|
|
146
|
+
|
|
147
|
+
已验证:
|
|
148
|
+
|
|
149
|
+
- `npx jest`:13 套 156 个用例全过(新增 17 个)
|
|
150
|
+
- ESLint:新增文件 0 error;改动文件只剩改动前就存在的既有错误(全库 132 个,`npm run lint` 本就不通过)
|
|
151
|
+
|
|
152
|
+
**未做真机联调**,以下回归项待验:
|
|
153
|
+
|
|
154
|
+
1. 配了脱敏的字段列,四种导出(列表 / 条件 / 选择 / 明细)文件中均无该列;
|
|
155
|
+
2. 两种规则方向:`type=2 + 角色匹配`、`type=1 + 角色不匹配`;
|
|
156
|
+
3. 未配脱敏的列完全不受影响;
|
|
157
|
+
4. **分组表头 2 层 / 3 层下剔除叶子列后表头不错位**(`loopDo` 会重算 `colSpan/rowSpan`,本轮风险最高的一处);
|
|
158
|
+
5. 用户信息接口未返回时立即点导出 → 走 fail-closed 而非明文导出;
|
|
159
|
+
6. 页面遮罩显示效果与改造前一致(验证 `initShowType` 重构无回归);
|
|
160
|
+
7. vxe 工具栏导出(若宿主开启了该按钮)。
|
|
161
|
+
|
|
162
|
+
## 九、同步提示
|
|
163
|
+
|
|
164
|
+
- `textMaskUtil.js` 属包内新增文件,海尔分支(stdxx)整体覆盖时需一并带上,否则 `form-item-wrapper.vue` 与两个容器的 import 会解析失败;
|
|
165
|
+
- 改动 5 落在 `components/table/index.js`(通用表格默认配置),影响面超出 xform,同步时注意与分支上的本地修改合并。
|
package/src/components/xform/form-designer/form-widget/container-widget/data-table-widget.vue
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
<div class="fr">
|
|
66
66
|
<!-- <vxe-button icon="el-icon-view" class="button-sty" @click="searchEvent">显示总记录数</vxe-button> -->
|
|
67
67
|
<vxe-button icon="el-icon-brush" class="button-sty" @click="resetEvent" type="text" status="primary"
|
|
68
|
-
plain
|
|
68
|
+
plain>刷新
|
|
69
69
|
</vxe-button>
|
|
70
70
|
|
|
71
71
|
<vxe-button status="warning" icon="el-icon-search" class="button-sty" @click="searchEvent">搜索
|
|
@@ -6,7 +6,16 @@
|
|
|
6
6
|
v-bind="dialogConfig"
|
|
7
7
|
@close="close"
|
|
8
8
|
>
|
|
9
|
-
|
|
9
|
+
<!-- host:"list" —— 承载列表宿主页(自带「常规(详情)/列表」页签),行点击进详情等行为与整页一致。
|
|
10
|
+
list.vue 根节点自带 #containt,本分支不再套 id,避免嵌套重复。 -->
|
|
11
|
+
<div
|
|
12
|
+
class="cont designer-view fd-list-host"
|
|
13
|
+
v-bind="bodyConfig"
|
|
14
|
+
v-if="listHost"
|
|
15
|
+
>
|
|
16
|
+
<list-view-host :formCode="listFormCode" :param="dialogParam" />
|
|
17
|
+
</div>
|
|
18
|
+
<div class="cont designer-view" v-bind="bodyConfig" id="containt" v-else>
|
|
10
19
|
<v-form-render
|
|
11
20
|
:form-json.sync="formJson"
|
|
12
21
|
:form-data="formData"
|
|
@@ -22,6 +31,7 @@
|
|
|
22
31
|
v-bind="formConfig"
|
|
23
32
|
:param="dialogParam"
|
|
24
33
|
class="data-table_height"
|
|
34
|
+
@hook:mounted="handleFormMounted"
|
|
25
35
|
/>
|
|
26
36
|
</div>
|
|
27
37
|
<span
|
|
@@ -34,7 +44,27 @@
|
|
|
34
44
|
<i class="el-icon-close el-icon"></i>
|
|
35
45
|
{{ $t2("取 消", "system.button.cancel2") }}
|
|
36
46
|
</el-button>
|
|
37
|
-
|
|
47
|
+
<!-- 模板配了 formConfig.actionButtons 时,用配置按钮替代「确定」(取消保持不变) -->
|
|
48
|
+
<template v-if="visibleFooterButtons">
|
|
49
|
+
<el-button
|
|
50
|
+
v-for="btn in visibleFooterButtons"
|
|
51
|
+
:key="btn.code"
|
|
52
|
+
:type="btn.type || 'primary'"
|
|
53
|
+
:plain="btn.plain === true"
|
|
54
|
+
class="button-sty"
|
|
55
|
+
:loading="!!btnLoadingMap[btn.code]"
|
|
56
|
+
@click="handleActionButton(btn)"
|
|
57
|
+
>
|
|
58
|
+
<i v-if="btn.icon" :class="[btn.icon, 'el-icon']"></i>
|
|
59
|
+
{{ btnLabel(btn) }}
|
|
60
|
+
</el-button>
|
|
61
|
+
</template>
|
|
62
|
+
<el-button
|
|
63
|
+
v-else-if="confirmButtonVisible"
|
|
64
|
+
type="primary"
|
|
65
|
+
@click="dialogSubmit"
|
|
66
|
+
class="button-sty"
|
|
67
|
+
>
|
|
38
68
|
<i class="el-icon-check el-icon"></i>
|
|
39
69
|
{{ $t2("确 定", "system.button.confirm2") }}
|
|
40
70
|
</el-button>
|
|
@@ -44,9 +74,22 @@
|
|
|
44
74
|
|
|
45
75
|
<script>
|
|
46
76
|
import VFormRender from "../../../../../components/xform/form-render/index.vue";
|
|
77
|
+
import {
|
|
78
|
+
resolveActionButtons,
|
|
79
|
+
hasButtonPermission,
|
|
80
|
+
isButtonVisible,
|
|
81
|
+
runActionButton,
|
|
82
|
+
} from "@base/components/formDialog/actionButtons";
|
|
47
83
|
|
|
48
84
|
export default {
|
|
49
|
-
components: {
|
|
85
|
+
components: {
|
|
86
|
+
VFormRender,
|
|
87
|
+
// 列表宿主页(x-tabs:常规/列表/其他页签/多标签编辑),仅 host:"list" 时加载
|
|
88
|
+
ListViewHost: () =>
|
|
89
|
+
import(
|
|
90
|
+
/* webpackChunkName: "form-list-host" */ "@base/views/user/form/view/list.vue"
|
|
91
|
+
),
|
|
92
|
+
},
|
|
50
93
|
props: {
|
|
51
94
|
visiable: Boolean,
|
|
52
95
|
option: Object,
|
|
@@ -76,7 +119,11 @@ export default {
|
|
|
76
119
|
currentLayoutType: null,
|
|
77
120
|
dataId: null,
|
|
78
121
|
formConfig: {},
|
|
79
|
-
confirmButtonVisible: true
|
|
122
|
+
confirmButtonVisible: true,
|
|
123
|
+
// 模板 formConfig.actionButtons(权限过滤后);为空则底部保持默认「确定」
|
|
124
|
+
customButtons: null,
|
|
125
|
+
btnLoadingMap: {},
|
|
126
|
+
formRefMounted: false,
|
|
80
127
|
};
|
|
81
128
|
},
|
|
82
129
|
computed: {
|
|
@@ -86,6 +133,13 @@ export default {
|
|
|
86
133
|
dialogParam() {
|
|
87
134
|
return this.option?.param;
|
|
88
135
|
},
|
|
136
|
+
// host:"list" —— 用列表宿主页承载(带「常规/列表」页签),而非直接渲染单个表单
|
|
137
|
+
listHost() {
|
|
138
|
+
return this.option?.host === "list";
|
|
139
|
+
},
|
|
140
|
+
listFormCode() {
|
|
141
|
+
return this.option?.formCode;
|
|
142
|
+
},
|
|
89
143
|
/* formConfig() {
|
|
90
144
|
return this.option?.formConfig;
|
|
91
145
|
}, */
|
|
@@ -107,6 +161,15 @@ export default {
|
|
|
107
161
|
config.title = this.$t1(config.title);
|
|
108
162
|
return config;
|
|
109
163
|
},
|
|
164
|
+
// 自定义按钮渲染列表:权限已在 initCustomButtons 过滤,此处按 visible 脚本过滤
|
|
165
|
+
visibleFooterButtons() {
|
|
166
|
+
if (!this.customButtons || !this.customButtons.length) return null;
|
|
167
|
+
this.formRefMounted; // eslint-disable-line no-unused-expressions
|
|
168
|
+
let buttons = this.customButtons.filter((btn) =>
|
|
169
|
+
isButtonVisible(btn, this.$refs.vFormRef)
|
|
170
|
+
);
|
|
171
|
+
return buttons.length ? buttons : null;
|
|
172
|
+
},
|
|
110
173
|
bodyConfig() {
|
|
111
174
|
let config = {};
|
|
112
175
|
let classStr = this.currentLayoutType;
|
|
@@ -131,6 +194,8 @@ export default {
|
|
|
131
194
|
this.currentLayoutType = currentFormConfig.layoutType;
|
|
132
195
|
|
|
133
196
|
this.selectMulti = this.option.multiple ?? true;
|
|
197
|
+
// 列表宿主页自己会加载模板;仅在未指定标题时补取一次模板名做弹框标题
|
|
198
|
+
if (this.listHost && this.option.dialogConfig?.title) return;
|
|
134
199
|
this.getReportTemplate();
|
|
135
200
|
},
|
|
136
201
|
methods: {
|
|
@@ -162,6 +227,8 @@ export default {
|
|
|
162
227
|
success: (res) => {
|
|
163
228
|
let formTemplate = res.objx || {};
|
|
164
229
|
this.formTemplate = formTemplate;
|
|
230
|
+
// 列表宿主模式:只借模板名做标题,渲染与按钮均由宿主页自己负责
|
|
231
|
+
if (this.listHost) return;
|
|
165
232
|
this.formJson = formTemplate.formViewContent
|
|
166
233
|
? JSON.parse(formTemplate.formViewContent)
|
|
167
234
|
: {};
|
|
@@ -170,11 +237,47 @@ export default {
|
|
|
170
237
|
if (formConfig.searchDialogUniqueField) {
|
|
171
238
|
this.fieldKey = formConfig.searchDialogUniqueField;
|
|
172
239
|
}
|
|
240
|
+
this.initCustomButtons(formConfig);
|
|
173
241
|
|
|
174
242
|
this.showRender = true;
|
|
175
243
|
},
|
|
176
244
|
});
|
|
177
245
|
},
|
|
246
|
+
// 模板 actionButtons:option.buttons 可覆盖;权限 formCode:authName 在此一次性过滤
|
|
247
|
+
initCustomButtons(formConfig) {
|
|
248
|
+
let buttons = resolveActionButtons(this.option?.buttons, formConfig);
|
|
249
|
+
this.customButtons = buttons
|
|
250
|
+
? buttons.filter((btn) => hasButtonPermission(btn, this.formCode, this))
|
|
251
|
+
: null;
|
|
252
|
+
},
|
|
253
|
+
handleFormMounted() {
|
|
254
|
+
this.formRefMounted = true;
|
|
255
|
+
},
|
|
256
|
+
btnLabel(btn) {
|
|
257
|
+
return this.$t1 ? this.$t1(btn.label) : btn.label;
|
|
258
|
+
},
|
|
259
|
+
/**
|
|
260
|
+
* 自定义按钮点击:校验 → onClick 脚本 → done/closeAfter 收口。
|
|
261
|
+
* 收口时优先回调 option.onAction(payload, formRef, vm);
|
|
262
|
+
* 未提供则仅按 closeAfter 关闭(按钮自身脚本即业务处理,不再走 option.confirm)。
|
|
263
|
+
*/
|
|
264
|
+
handleActionButton(btn) {
|
|
265
|
+
let formRef = this.$refs.vFormRef;
|
|
266
|
+
if (!formRef || this.btnLoadingMap[btn.code]) return;
|
|
267
|
+
this.$set(this.btnLoadingMap, btn.code, true);
|
|
268
|
+
let clearLoading = () => this.$set(this.btnLoadingMap, btn.code, false);
|
|
269
|
+
runActionButton(btn, {
|
|
270
|
+
formRef,
|
|
271
|
+
host: this,
|
|
272
|
+
getSelection: () => [],
|
|
273
|
+
onSettle: (payload, close) => {
|
|
274
|
+
if (typeof this.option.onAction === "function") {
|
|
275
|
+
this.option.onAction(payload, formRef, this);
|
|
276
|
+
}
|
|
277
|
+
if (close) this.close();
|
|
278
|
+
},
|
|
279
|
+
}).then(clearLoading, clearLoading);
|
|
280
|
+
},
|
|
178
281
|
close() {
|
|
179
282
|
let formRef = this.$refs.vFormRef;
|
|
180
283
|
this.showDialog = false;
|
|
@@ -206,6 +309,14 @@ export default {
|
|
|
206
309
|
.list-dialog {
|
|
207
310
|
.el-dialog__body {
|
|
208
311
|
height: calc(100% - 42px);
|
|
312
|
+
// host:"list":.cont 已有确定高度,把它传给 list.vue 根节点 #containt,
|
|
313
|
+
// .tab-box 自身是 height:100%,父链没高度页签内容区会塌成一条
|
|
314
|
+
.cont.fd-list-host {
|
|
315
|
+
padding: 0;
|
|
316
|
+
::v-deep > #containt {
|
|
317
|
+
height: 100%;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
209
320
|
.cont {
|
|
210
321
|
height: calc(100vh - 210px);
|
|
211
322
|
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<el-drawer v-bind="dialogConfig" @close="close" :visible.sync="showDialog">
|
|
3
|
-
|
|
3
|
+
<!-- host:"list" —— 承载列表宿主页(自带「常规(详情)/列表」页签),行点击进详情等行为与整页一致。
|
|
4
|
+
list.vue 根节点自带 #containt,本分支不再套 id,避免嵌套重复。 -->
|
|
5
|
+
<div class="cont fd-list-host" v-bind="bodyConfig" v-if="listHost">
|
|
6
|
+
<list-view-host :formCode="listFormCode" :param="dialogParam"/>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="cont" v-bind="bodyConfig" id="containt" v-else>
|
|
4
9
|
<v-form-render :form-json.sync="formJson" :form-data="formData" :option-data="optionData" ref="vFormRef"
|
|
5
10
|
:reportTemplate.sync="formTemplate" :conditionParam.sync="conditionParam"
|
|
6
11
|
:formInsData.sync="formInsData" v-if="showRender" visible-key="showRender" :parent-target="_self"
|
|
7
12
|
@reload="reload" :param="dialogParam"
|
|
8
|
-
class="data-table_height"/>
|
|
13
|
+
class="data-table_height" @hook:mounted="handleFormMounted"/>
|
|
9
14
|
</div>
|
|
10
15
|
<span class="dialog-footer" v-if="option.showFooter!==false" v-bind="option.footerConfig">
|
|
11
16
|
<el-button type="primary" plain class="button-sty" @click="close">
|
|
12
17
|
<i class="el-icon-close el-icon"></i>
|
|
13
18
|
{{ $t2('取 消', 'system.button.cancel2') }}
|
|
14
19
|
</el-button>
|
|
15
|
-
|
|
20
|
+
<!-- 模板配了 formConfig.actionButtons 时,用配置按钮替代「确定」(取消保持不变) -->
|
|
21
|
+
<template v-if="visibleFooterButtons">
|
|
22
|
+
<el-button v-for="btn in visibleFooterButtons" :key="btn.code" :type="btn.type || 'primary'"
|
|
23
|
+
:plain="btn.plain === true" class="button-sty" :loading="!!btnLoadingMap[btn.code]"
|
|
24
|
+
@click="handleActionButton(btn)">
|
|
25
|
+
<i v-if="btn.icon" :class="[btn.icon, 'el-icon']"></i>
|
|
26
|
+
{{ btnLabel(btn) }}
|
|
27
|
+
</el-button>
|
|
28
|
+
</template>
|
|
29
|
+
<el-button v-else type="primary" @click="dialogSubmit" class="button-sty">
|
|
16
30
|
<i class="el-icon-check el-icon"></i>
|
|
17
31
|
{{ $t2('确 定', 'system.button.confirm2') }}
|
|
18
32
|
</el-button>
|
|
@@ -22,9 +36,22 @@
|
|
|
22
36
|
|
|
23
37
|
<script>
|
|
24
38
|
import VFormRender from "../../../../../components/xform/form-render/index.vue";
|
|
39
|
+
import {
|
|
40
|
+
resolveActionButtons,
|
|
41
|
+
hasButtonPermission,
|
|
42
|
+
isButtonVisible,
|
|
43
|
+
runActionButton,
|
|
44
|
+
} from "@base/components/formDialog/actionButtons";
|
|
25
45
|
|
|
26
46
|
export default {
|
|
27
|
-
components: {
|
|
47
|
+
components: {
|
|
48
|
+
VFormRender,
|
|
49
|
+
// 列表宿主页(x-tabs:常规/列表/其他页签/多标签编辑),仅 host:"list" 时加载
|
|
50
|
+
ListViewHost: () =>
|
|
51
|
+
import(
|
|
52
|
+
/* webpackChunkName: "form-list-host" */ "@base/views/user/form/view/list.vue"
|
|
53
|
+
),
|
|
54
|
+
},
|
|
28
55
|
props: {
|
|
29
56
|
visiable: Boolean,
|
|
30
57
|
option: Object
|
|
@@ -52,7 +79,11 @@ export default {
|
|
|
52
79
|
conditionParam: null,
|
|
53
80
|
formInsData: null,
|
|
54
81
|
$grid: null,
|
|
55
|
-
currentLayoutType: null
|
|
82
|
+
currentLayoutType: null,
|
|
83
|
+
// 模板 formConfig.actionButtons(权限过滤后);为空则底部保持默认「确定」
|
|
84
|
+
customButtons: null,
|
|
85
|
+
btnLoadingMap: {},
|
|
86
|
+
formRefMounted: false
|
|
56
87
|
};
|
|
57
88
|
},
|
|
58
89
|
computed: {
|
|
@@ -62,6 +93,13 @@ export default {
|
|
|
62
93
|
dialogParam() {
|
|
63
94
|
return this.option?.param;
|
|
64
95
|
},
|
|
96
|
+
// host:"list" —— 用列表宿主页承载(带「常规/列表」页签),而非直接渲染单个表单
|
|
97
|
+
listHost() {
|
|
98
|
+
return this.option?.host === "list";
|
|
99
|
+
},
|
|
100
|
+
listFormCode() {
|
|
101
|
+
return this.option?.formCode;
|
|
102
|
+
},
|
|
65
103
|
dialogConfig() {
|
|
66
104
|
let customClass = "dialog-style list-dialog dialog-checkbox pd_0";
|
|
67
105
|
if (this.option.dialogConfig?.customClass) {
|
|
@@ -83,6 +121,15 @@ export default {
|
|
|
83
121
|
config.title = this.$t1(config.title);
|
|
84
122
|
return config;
|
|
85
123
|
},
|
|
124
|
+
// 自定义按钮渲染列表:权限已在 initCustomButtons 过滤,此处按 visible 脚本过滤
|
|
125
|
+
visibleFooterButtons() {
|
|
126
|
+
if (!this.customButtons || !this.customButtons.length) return null;
|
|
127
|
+
this.formRefMounted; // eslint-disable-line no-unused-expressions
|
|
128
|
+
let buttons = this.customButtons.filter((btn) =>
|
|
129
|
+
isButtonVisible(btn, this.$refs.vFormRef)
|
|
130
|
+
);
|
|
131
|
+
return buttons.length ? buttons : null;
|
|
132
|
+
},
|
|
86
133
|
bodyConfig() {
|
|
87
134
|
let config = {};
|
|
88
135
|
let classStr = this.currentLayoutType
|
|
@@ -101,6 +148,8 @@ export default {
|
|
|
101
148
|
this.currentLayoutType = currentFormConfig.layoutType
|
|
102
149
|
|
|
103
150
|
this.selectMulti = this.option.multiple ?? true;
|
|
151
|
+
// 列表宿主页自己会加载模板;仅在未指定标题时补取一次模板名做抽屉标题
|
|
152
|
+
if (this.listHost && this.option.dialogConfig?.title) return;
|
|
104
153
|
this.getReportTemplate();
|
|
105
154
|
},
|
|
106
155
|
methods: {
|
|
@@ -130,12 +179,15 @@ export default {
|
|
|
130
179
|
success: res => {
|
|
131
180
|
let formTemplate = res.objx || {};
|
|
132
181
|
this.formTemplate = formTemplate;
|
|
182
|
+
// 列表宿主模式:只借模板名做标题,渲染与按钮均由宿主页自己负责
|
|
183
|
+
if (this.listHost) return;
|
|
133
184
|
this.formJson = formTemplate.formViewContent ? JSON.parse(formTemplate.formViewContent) : {};
|
|
134
185
|
this.layoutType = this.formJson.formConfig.layoutType
|
|
135
186
|
let formConfig = this.formJson.formConfig;
|
|
136
187
|
if (formConfig.searchDialogUniqueField) {
|
|
137
188
|
this.fieldKey = formConfig.searchDialogUniqueField;
|
|
138
189
|
}
|
|
190
|
+
this.initCustomButtons(formConfig);
|
|
139
191
|
|
|
140
192
|
this.showRender = true;
|
|
141
193
|
|
|
@@ -143,6 +195,41 @@ export default {
|
|
|
143
195
|
}
|
|
144
196
|
});
|
|
145
197
|
},
|
|
198
|
+
// 模板 actionButtons:option.buttons 可覆盖;权限 formCode:authName 在此一次性过滤
|
|
199
|
+
initCustomButtons(formConfig) {
|
|
200
|
+
let buttons = resolveActionButtons(this.option?.buttons, formConfig);
|
|
201
|
+
this.customButtons = buttons
|
|
202
|
+
? buttons.filter((btn) => hasButtonPermission(btn, this.formCode, this))
|
|
203
|
+
: null;
|
|
204
|
+
},
|
|
205
|
+
handleFormMounted() {
|
|
206
|
+
this.formRefMounted = true;
|
|
207
|
+
},
|
|
208
|
+
btnLabel(btn) {
|
|
209
|
+
return this.$t1 ? this.$t1(btn.label) : btn.label;
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* 自定义按钮点击:校验 → onClick 脚本 → done/closeAfter 收口。
|
|
213
|
+
* 收口时优先回调 option.onAction(payload, formRef, vm);
|
|
214
|
+
* 未提供则仅按 closeAfter 关闭(按钮自身脚本即业务处理,不再走 option.confirm)。
|
|
215
|
+
*/
|
|
216
|
+
handleActionButton(btn) {
|
|
217
|
+
let formRef = this.$refs.vFormRef;
|
|
218
|
+
if (!formRef || this.btnLoadingMap[btn.code]) return;
|
|
219
|
+
this.$set(this.btnLoadingMap, btn.code, true);
|
|
220
|
+
let clearLoading = () => this.$set(this.btnLoadingMap, btn.code, false);
|
|
221
|
+
runActionButton(btn, {
|
|
222
|
+
formRef,
|
|
223
|
+
host: this,
|
|
224
|
+
getSelection: () => [],
|
|
225
|
+
onSettle: (payload, close) => {
|
|
226
|
+
if (typeof this.option.onAction === "function") {
|
|
227
|
+
this.option.onAction(payload, formRef, this);
|
|
228
|
+
}
|
|
229
|
+
if (close) this.close();
|
|
230
|
+
},
|
|
231
|
+
}).then(clearLoading, clearLoading);
|
|
232
|
+
},
|
|
146
233
|
close() {
|
|
147
234
|
let formRef = this.$refs.vFormRef;
|
|
148
235
|
this.showDialog = false;
|
|
@@ -172,6 +259,16 @@ export default {
|
|
|
172
259
|
.el-dialog__body {
|
|
173
260
|
height: calc(100% - 42px);
|
|
174
261
|
|
|
262
|
+
// host:"list":.cont 已有确定高度,把它传给 list.vue 根节点 #containt,
|
|
263
|
+
// .tab-box 自身是 height:100%,父链没高度页签内容区会塌成一条
|
|
264
|
+
.cont.fd-list-host {
|
|
265
|
+
padding: 0;
|
|
266
|
+
|
|
267
|
+
::v-deep > #containt {
|
|
268
|
+
height: 100%;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
175
272
|
.cont {
|
|
176
273
|
height: calc(100vh - 210px);
|
|
177
274
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<div class="clearfix screen-btns">
|
|
28
28
|
<div class="fr">
|
|
29
29
|
<vxe-button icon="el-icon-brush" class="button-sty" @click="resetEvent" type="text" status="primary"
|
|
30
|
-
plain
|
|
30
|
+
plain>刷新
|
|
31
31
|
</vxe-button>
|
|
32
32
|
<vxe-button status="warning" icon="el-icon-search" class="button-sty" @click="searchEvent">搜索</vxe-button>
|
|
33
33
|
</div>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<div class="clearfix screen-btns">
|
|
28
28
|
<div class="fr">
|
|
29
29
|
<vxe-button icon="el-icon-brush" class="button-sty" @click="resetEvent" type="text" status="primary"
|
|
30
|
-
plain
|
|
30
|
+
plain>刷新
|
|
31
31
|
</vxe-button>
|
|
32
32
|
<vxe-button status="warning" icon="el-icon-search" class="button-sty" @click="searchEvent">搜索</vxe-button>
|
|
33
33
|
</div>
|