cloud-web-corejs 1.0.54-dev.797 → 1.0.54-dev.799
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/components/baseTabs/mixins.js +11 -12
- package/src/components/bizTab/BizBillActions.vue +129 -0
- package/src/components/bizTab/BizTabListPage.vue +391 -0
- package/src/components/bizTab/README.md +378 -0
- package/src/components/bizTab/billTableUtil.js +48 -0
- package/src/components/bizTab/billValidators.js +136 -0
- package/src/components/bizTab/bizBillDetailMixin.js +687 -0
- package/src/components/bizTab/index.js +24 -0
- package/src/components/excelExport/exportFieldDialog.vue +373 -367
- package/src/components/excelExport/exportType.js +49 -0
- package/src/components/excelExport/mixins.js +1153 -1141
- package/src/components/vb-tabs/x-tabs.vue +167 -143
- package/src/components/wf/wfUtil.js +327 -292
- package/src/components/xform/form-designer/designer.js +56 -8
- package/src/components/xform/form-designer/form-widget/container-widget/detail-widget.vue +59 -5
- package/src/components/xform/form-designer/form-widget/field-widget/baseAttachment-widget.vue +60 -8
- package/src/components/xform/form-designer/form-widget/field-widget/form-item-wrapper.vue +8 -2
- package/src/components/xform/form-designer/form-widget/field-widget/select-export-item-button-widget.vue +120 -92
- package/src/components/xform/form-designer/form-widget/field-widget/vabUpload-widget.vue +63 -94
- package/src/components/xform/form-designer/index.vue +0 -1
- package/src/components/xform/form-designer/setting-panel/dataSource-setting.vue +397 -397
- package/src/components/xform/form-designer/setting-panel/form-dynamicField-setting.vue +602 -602
- package/src/components/xform/form-designer/setting-panel/form-setting.vue +13 -13
- package/src/components/xform/form-designer/setting-panel/option-items-setting.vue +585 -585
- package/src/components/xform/form-designer/setting-panel/property-editor/a-link-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/a-text-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/alignType-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/censusClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/colorClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/commonAttributeEnabled-editor.vue +3 -3
- package/src/components/xform/form-designer/setting-panel/property-editor/container-data-table/data-table-editor.vue +1154 -1154
- package/src/components/xform/form-designer/setting-panel/property-editor/container-detail/detail-editor.vue +96 -80
- package/src/components/xform/form-designer/setting-panel/property-editor/container-detail-plain/detail-plain-editor.vue +75 -75
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid/colHeight-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid/gutter-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid-col/grid-col-height-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid-col/grid-col-offset-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid-col/grid-col-pull-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid-col/grid-col-push-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-grid-col/grid-col-span-editor.vue +4 -4
- package/src/components/xform/form-designer/setting-panel/property-editor/container-h5-card/h5-card-editor.vue +124 -124
- package/src/components/xform/form-designer/setting-panel/property-editor/container-panel/height-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-panel/width-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-tab/tab-customClass-editor.vue +119 -119
- package/src/components/xform/form-designer/setting-panel/property-editor/container-tab/tabClass-editor.vue +45 -45
- package/src/components/xform/form-designer/setting-panel/property-editor/container-table/table-dynamicField-editor.vue +344 -344
- package/src/components/xform/form-designer/setting-panel/property-editor/container-table-cell/cellHeight-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-table-cell/cellWidth-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-tree/tree-customClass-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/container-tree-pane/tree-pane-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-vf-dialog/cancelButtonLabel-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-vf-dialog/okButtonLabel-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-vf-dialog/title-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/container-vf-dialog/width-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/containerClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/customClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/defaultValue-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/descriptionsItems-editor.vue +4 -4
- package/src/components/xform/form-designer/setting-panel/property-editor/downloadButtonFlag-editor.vue +3 -3
- package/src/components/xform/form-designer/setting-panel/property-editor/dropdownFlag-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/dsName-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/echartHeight-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/endPlaceholder-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-accessUrl/accessUrl-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/field-area-select/areaDataType-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-area-select/areaMinLevel-editor.vue +50 -50
- package/src/components/xform/form-designer/setting-panel/property-editor/field-area-select/areaName-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-autocomplete/autocomplete-vabSearchName-editor.vue +7 -7
- package/src/components/xform/form-designer/setting-panel/property-editor/field-button/addTableData-event-editor.vue +84 -84
- package/src/components/xform/form-designer/setting-panel/property-editor/field-button/button-type-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-button/clickBindEvent-editor.vue +97 -97
- package/src/components/xform/form-designer/setting-panel/property-editor/field-button/form-host-event-editor.vue +188 -188
- package/src/components/xform/form-designer/setting-panel/property-editor/field-button/search-dialog-event-editor.vue +237 -237
- package/src/components/xform/form-designer/setting-panel/property-editor/field-common/keyName-editor.vue +74 -67
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date/date-dateLimit-editor.vue +4 -4
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date/date-defaultValue-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date/date-format-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date/date-type-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date/date-valueFormat-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date-range/date-range-defaultTime-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date-range/date-range-defaultValue-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date-range/date-range-format-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date-range/date-range-type-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-date-range/date-range-valueFormat-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-divider/contentPosition-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-dropdown-menu/dropdown-menu-editor.vue +59 -59
- package/src/components/xform/form-designer/setting-panel/property-editor/field-echart/echart-bar-editor.vue +11 -11
- package/src/components/xform/form-designer/setting-panel/property-editor/field-echart/echart-category-editor.vue +10 -10
- package/src/components/xform/form-designer/setting-panel/property-editor/field-echart/echart-pie-editor.vue +12 -12
- package/src/components/xform/form-designer/setting-panel/property-editor/field-file-upload/file-upload-fileTypes-editor.vue +44 -44
- package/src/components/xform/form-designer/setting-panel/property-editor/field-html-text/htmlContent-editor.vue +29 -29
- package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import-button-editor.vue +6 -6
- package/src/components/xform/form-designer/setting-panel/property-editor/field-import-button/import2-button-editor.vue +3 -3
- package/src/components/xform/form-designer/setting-panel/property-editor/field-number/controlsPosition-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-picture-upload/picture-upload-fileTypes-editor.vue +44 -44
- package/src/components/xform/form-designer/setting-panel/property-editor/field-print-button/print-button-editor.vue +115 -115
- package/src/components/xform/form-designer/setting-panel/property-editor/field-print-button/print-detail-button-editor.vue +115 -115
- package/src/components/xform/form-designer/setting-panel/property-editor/field-rate/highThreshold-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-rate/lowThreshold-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-rate/rate-defaultValue-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-rate/rate-max-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-script/script-editor.vue +4 -4
- package/src/components/xform/form-designer/setting-panel/property-editor/field-script-input/script-input-editor.vue +4 -4
- package/src/components/xform/form-designer/setting-panel/property-editor/field-static-text/fontSize-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-static-text/textContent-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-status/field-status-editor.vue +3 -3
- package/src/components/xform/form-designer/setting-panel/property-editor/field-switch/activeText-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-switch/inactiveText-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-switch/switchWidth-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/select-export-button-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/select-export-item-button-editor.vue +74 -71
- package/src/components/xform/form-designer/setting-panel/property-editor/field-table-export-button/table-export-button-editor.vue +3 -3
- package/src/components/xform/form-designer/setting-panel/property-editor/field-time/time-defaultValue-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-time/time-format-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-time/time-timeLimit-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-time-range/time-range-defaultValue-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-time-range/time-range-format-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-vabSearch/vabSearchField-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/field-vabSearch/vabSearchName-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/field-vabUpload/field-vabUpload-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-vabUpload2/field-vabUpload2-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/field-vue-page/vue-page-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/fileMaxSize-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/fileTypesSelect.vue +176 -176
- package/src/components/xform/form-designer/setting-panel/property-editor/formScriptEnabled-editor.vue +2 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/formatType-editor.vue +2 -2
- package/src/components/xform/form-designer/setting-panel/property-editor/formula-editor.vue +972 -972
- package/src/components/xform/form-designer/setting-panel/property-editor/iconClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/label-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/labelAlign-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/labelColor-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/labelIconPosition-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/labelWidth-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/limit-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/max-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/maxLength-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/min-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/minLength-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/multipleLimit-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/name-editor.vue +178 -178
- package/src/components/xform/form-designer/setting-panel/property-editor/oplog-editor.vue +3 -3
- package/src/components/xform/form-designer/setting-panel/property-editor/placeholder-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/precision-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/project-tag-editor.vue +6 -6
- package/src/components/xform/form-designer/setting-panel/property-editor/requiredHint-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/rows-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/space-editor-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/startPlaceholder-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/step-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/styleTableClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/styleTitClass-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/tempStorage-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/type-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/uploadTip-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/uploadURL-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/urlValueEnabled-editor.vue +91 -0
- package/src/components/xform/form-designer/setting-panel/property-editor/validation-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/validationHint-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/property-editor/widgetWidth-editor.vue +1 -1
- package/src/components/xform/form-designer/setting-panel/propertyRegister.js +1 -0
- package/src/components/xform/form-designer/widget-panel/index.vue +3 -1
- package/src/components/xform/form-designer/widget-panel/widgetsConfig.js +55 -13
- package/src/components/xform/form-render/container-item/data-table-mixin copy.js +5910 -0
- package/src/components/xform/form-render/container-item/data-table-mixin.js +59 -13
- package/src/components/xform/form-render/container-item/detail-item.vue +52 -11
- package/src/components/xform/form-render/container-item/tab-item.vue +6 -2
- package/src/components/xform/form-render/indexMixin.js +56 -16
- package/src/components/xform/lang/zh-CN.js +2 -1
- package/src/components/xform/utils/attachmentValueUtil.js +239 -0
- package/src/components/xform/utils/util.js +27 -0
- package/src/mixins/wf/index.js +40 -38
- package/src/router/modules/customer.js +50 -0
- package/src/store/config/index.js +46 -0
- package/src/utils/scrollUtil.js +25 -0
- package/src/views/demo/bizTab/bill/billConfig.js +41 -0
- package/src/views/demo/bizTab/bill/detail.vue +436 -0
- package/src/views/demo/bizTab/bill/list.vue +288 -0
- package/src/views/demo/bizTab/bill/productDialog.vue +176 -0
- package/src/views/demo/bizTab/simple/billApi.js +123 -0
- package/src/views/demo/bizTab/simple/detail.vue +407 -0
- package/src/views/demo/bizTab/simple/list.vue +217 -0
- package/src/views/demo/bizTab/standalone/index.vue +121 -0
- package/src/views/demo/bizTab/wf/billConfig.js +44 -0
- package/src/views/demo/bizTab/wf/detail.vue +661 -0
- package/src/views/demo/bizTab/wf/list.vue +242 -0
- package/src/views/user/outLink/cc_form_view.vue +185 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# bizTab —— 手写 Vue 页面的「列表 + 多详情页签 + 流程」通用组件
|
|
2
|
+
|
|
3
|
+
给**手写页面**用的。低代码那套(`views/user/form/view/list.vue` + xform)有自己的一份实现,两边互不影响,本组件没有改动它。
|
|
4
|
+
|
|
5
|
+
三个可运行示例,路由都已注册在 `src/router/modules/customer.js`(本地专用;消费应用走后台菜单,见「菜单接入」):
|
|
6
|
+
|
|
7
|
+
| demo | 路径 | 本地直达 | 数据通道 | 说明 |
|
|
8
|
+
|---|---|---|---|---|
|
|
9
|
+
| A 结构版 | `views/demo/bizTab/simple/` | `/demo/bizTab/simple` | 前端 mock | 不打后台、不接流程,任何环境都能跑,看页签/翻单/脏数据拦截 |
|
|
10
|
+
| B formCode 版 | `views/demo/bizTab/bill/` | `/demo/bizTab/bill` | 通用 `bd_api` + formCode | 真实单据 + 真实流程。写法逐条对齐 stdxx 的 `views/pmd/product/product_change/`,含产品选择弹框、子表增删 |
|
|
11
|
+
| C 自定义接口版 | `views/demo/bizTab/wf/` | `/demo/bizTab/wf` | 自己写的控制器 | 真实单据 + 真实流程。写法对齐 std 那套手写页面(也就是本仓库的 `views/pmd/price/ladderPrice/`),含机构/产品两张子表、行内必填校验、附件必填、操作日志、作废按钮、流程中放开字段与整张子表 |
|
|
12
|
+
| D 独立模式 | `views/demo/bizTab/standalone/` | `/demo/bizTab/standalone?id=123` | 复用 C 的详情 | **不在页签里**用同一个详情组件(待办直开、老页面内嵌走的就是这条),验证 `_dataId` + `$reloadHandle` 退路 |
|
|
13
|
+
|
|
14
|
+
**要看流程就跑 B 或 C**(A 不接流程)。B、C 的后台前置条件分别写在各自 `billConfig.js` 顶部;两者的页签/流程用法完全相同,差别只在那三个业务钩子里的请求,照你自己单据的接口形态挑一个抄。
|
|
15
|
+
|
|
16
|
+
## 能力
|
|
17
|
+
|
|
18
|
+
1. 新增、查看详情都开独立页签;同一单据不重复开,新增可以连开多个互不覆盖
|
|
19
|
+
2. 「保存并提交流程」按钮在**新增页**就能看到,一步完成保存 + 启动流程
|
|
20
|
+
3. 流程动作完成后按配置**刷新当前页签**(默认)或**关页签 → 回列表 → 刷新列表**
|
|
21
|
+
4. 审批点「通过」时自动保存单据(先校验、后保存、再提交任务)
|
|
22
|
+
5. 详情头部「上一单 / 下一单」翻单(列表把顺序快照传进来才有)
|
|
23
|
+
6. 关页签前拦一下未保存内容;关页签 / 切回列表页签时自动刷新列表
|
|
24
|
+
|
|
25
|
+
## 三个文件
|
|
26
|
+
|
|
27
|
+
| 文件 | 角色 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `BizTabListPage.vue` | 页签宿主壳。只管页签,不认识业务表单,详情内容走 `#detail` 插槽 |
|
|
30
|
+
| `bizBillDetailMixin.js` | 详情页 mixin。流程挂载、保存并提交、通过自动保存、翻单、提交后行为、校验 |
|
|
31
|
+
| `billValidators.js` | 数据级校验工具(附件/多选/子表行数的判空),mixin 的 `checkBillRequired` / `checkRowsRequired` 就是它 |
|
|
32
|
+
| `billTableUtil.js` | 表格必填的显示:`requiredHeader()` 列头星号、`buildRowProp()` 拼行内 form-item 的 prop 路径 |
|
|
33
|
+
| `BizBillActions.vue` | 详情头部按钮区。统一按钮的显隐、顺序与文案,从左到右固定为**翻单 → 提交流程 → 业务按钮(默认插槽)→ 重置 → 保存**(`#before` 插槽是"要排在翻单之前"的特例出口) |
|
|
34
|
+
|
|
35
|
+
宿主与详情之间靠 `provide/inject("bizTabHost")` + `#detail` 插槽透传的 `tab` 对象通信,不用 `$attrs['parent-target']` 层层穿透。
|
|
36
|
+
|
|
37
|
+
## 接入
|
|
38
|
+
|
|
39
|
+
三个组件**不做全局注册**(避免进主包,和首屏优化的懒化方向冲突),用到就局部注册:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
import BizTabListPage from "@base/components/bizTab/BizTabListPage.vue";
|
|
43
|
+
import BizBillActions from "@base/components/bizTab/BizBillActions.vue";
|
|
44
|
+
import { bizBillDetailMixin } from "@base/components/bizTab";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
消费应用想全局注册,自己在入口调 `Vue.use(require("cloud-web-corejs/src/components/bizTab").default)`。
|
|
48
|
+
|
|
49
|
+
### 列表页
|
|
50
|
+
|
|
51
|
+
```vue
|
|
52
|
+
<biz-tab-list-page
|
|
53
|
+
ref="bizTab"
|
|
54
|
+
titleField="sn"
|
|
55
|
+
:behavior="tabBehavior"
|
|
56
|
+
@refresh-list="searchEvent"
|
|
57
|
+
>
|
|
58
|
+
<template #list>
|
|
59
|
+
<vxe-grid ref="table-m1" v-bind="vxeOption">…</vxe-grid>
|
|
60
|
+
</template>
|
|
61
|
+
<template #detail="{ tab }">
|
|
62
|
+
<bill-detail :tab="tab" />
|
|
63
|
+
</template>
|
|
64
|
+
<!-- 默认插槽在页签之外:各类选择弹框放这里 -->
|
|
65
|
+
<some-dialog v-if="showX" :visiable.sync="showX" />
|
|
66
|
+
</biz-tab-list-page>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
this.$refs.bizTab.openAdd(); // 新增开页签
|
|
71
|
+
this.$refs.bizTab.openDetail(row); // 详情开页签(已开则激活复用)
|
|
72
|
+
// 带翻单快照:详情头部就有「上一单/下一单」
|
|
73
|
+
this.$refs.bizTab.openDetail(row, null, { navRows: rows, navIndex: rowIndex });
|
|
74
|
+
// 新增页签也要翻单就一样传:保存成功后新单被插到序列首位,按钮随即可用。
|
|
75
|
+
// 不传 = 新增页保存完也没有翻单按钮(快照只有列表拿得到,组件无从得知)
|
|
76
|
+
this.$refs.bizTab.openAdd(null, { navRows: rows });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### props
|
|
80
|
+
|
|
81
|
+
| prop | 类型 | 默认 | 说明 |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `value` (v-model) | String | — | 当前页签名,可选;不绑也能用 |
|
|
84
|
+
| `listLabel` | String | `列表` | 列表页签标题 |
|
|
85
|
+
| `titleField` | String | `name` | 页签标题取详情数据的哪个字段(如 `sn`) |
|
|
86
|
+
| `addTitle` | String | `新增` | 新增页签初始标题;第二个起自动加序号(新增2/新增3),保存后换成 `titleField` 的值 |
|
|
87
|
+
| `behavior` | String / Object | `refreshCurrent` | 流程动作完成后的页签行为,见下 |
|
|
88
|
+
| `beforeTabClose` | Function | — | `(tab, detail) => Boolean \| Promise`,返回 false / reject 中止关闭。不传走内置判断 |
|
|
89
|
+
| `confirmOnClose` | Boolean | `true` | 内置关闭确认开关(自己传了 `beforeTabClose` 时无效) |
|
|
90
|
+
| `refreshOnActivateList` | Boolean | `true` | 激活列表页签(含关掉详情自动回列表)时自动 `@refresh-list` |
|
|
91
|
+
| `maxTabs` | Number | `10` | 页签上限,超了给提示(x-tabs 内部到 30 会直接顶掉第一个) |
|
|
92
|
+
|
|
93
|
+
事件:`@refresh-list`、`@tab-change(name)`、`@tab-click(tab)`、`@tab-removed(name)`。
|
|
94
|
+
|
|
95
|
+
内置关闭确认的判定顺序:详情实现了 `isBillDirty()` → 问它;否则只拦「还没保存过的新增页签」。
|
|
96
|
+
|
|
97
|
+
**未保存的修改在三条路径上都会拦**(都以 `isBillDirty()` 为判据):关页签要确认;**翻单**要确认(放弃修改才翻);**从列表重复打开同一单**时若详情是脏的,只激活页签、不重新取数(否则 `openEditTab` 的复用分支会重挂载,把改了半天的内容无声冲掉)。详情没实现 `isBillDirty()` 时后两条不拦。
|
|
98
|
+
|
|
99
|
+
#### behavior
|
|
100
|
+
|
|
101
|
+
- `"refreshCurrent"`:刷新当前页签,能看到最新流程信息(**默认**)
|
|
102
|
+
- `"closeToList"`:关掉当前页签 → 回列表 → 刷新列表
|
|
103
|
+
|
|
104
|
+
传字符串 = 所有动作统一;传对象 = 按动作分别配置,未列出的走 `default`:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
{ submit: "closeToList", agree: "closeToList", default: "refreshCurrent" }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
动作类型:`submit` 提交(启动流程) / `agree` 通过 / `reject` 驳回 / `interrupt` 中断撤回 / `transfer` 转办 / `talk` 沟通。
|
|
111
|
+
|
|
112
|
+
### 详情页
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
import { bizBillDetailMixin } from "@base/components/bizTab";
|
|
116
|
+
export default { mixins: [bizBillDetailMixin], … }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**必须实现**:
|
|
120
|
+
|
|
121
|
+
| 成员 | 说明 |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `billOptions` (computed/data) | 单据与流程配置,见下表 |
|
|
124
|
+
| `applyBillData(objx)` | 把数据灌进表单模型;`objx` 为 `null` 表示新增 |
|
|
125
|
+
| `saveBill({confirm, successMsg})` | 返回 `Promise<dataId>`,失败/取消返回 `null` |
|
|
126
|
+
| `buildLoadRequest(dataId)` | 返回 `{url, method, data}`,详情取数请求。**取数不走 `$http` 就整个覆盖 `loadBill()`**,此时不需要它(demo A 就是这样) |
|
|
127
|
+
|
|
128
|
+
**可覆盖**:
|
|
129
|
+
|
|
130
|
+
| 钩子 | 默认 |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `validateBill()` | 校验 `$refs.editForm` + `$refs.extraForm`(只挑绑了 `:model` 的),统一弹一次提示,最后跑 `validateBillData()` |
|
|
133
|
+
| `validateBillData()` | 返回 `null`。数据级校验钩子(子表必填、合计、业务规则),返回错误文案或 `false` 即中止;可返回 Promise |
|
|
134
|
+
| `getBillTitle()` | 取 `billModel[titleField]`(模型不叫 `billModel` 就覆盖 `getBillModel()`) |
|
|
135
|
+
| `canEditBill()` | 流程没启动就能改,启动了看 `wfInfo.toModify`。**字段禁用(`billDisabled`)与「重置/保存」显隐的唯一判据**,要叠加单据状态就覆盖它:<br>`if (this.hasWf) return !!(this.wfInfo && this.wfInfo.toModify); return this.isAdd || this.billModel.status === 0;` |
|
|
136
|
+
| `canSaveOnAgree()` | `wfInfo.toModify === true` |
|
|
137
|
+
| `canStartWf()` | `true`。按单据状态卡「提交流程」(如作废/已完结不许提交)就覆盖它 |
|
|
138
|
+
| `isBillDirty()` | 未实现。实现了它,关页签就按它提示未保存 |
|
|
139
|
+
|
|
140
|
+
**mixin 提供**:`dataId` / `isAdd` / `hasWf` / `wfInfo` / `billSaving` / `billSubmitting` / `billDisabled`、`loadBill()`、`saveHandle()`、`saveAndSubmitHandle()`、`startWfHandle()`、`reloadDetail()`、`gotoPrevBill()` / `gotoNextBill()`、`showSaveSubmitBtn` / `showWfStartBtn` / `billNavState` / `canPrevBill` / `canNextBill`。
|
|
141
|
+
|
|
142
|
+
`loadBill()` 要自己在 `mounted` 里调(不自动调,免得和宿主页面自己的初始化抢时序)。
|
|
143
|
+
|
|
144
|
+
#### billOptions
|
|
145
|
+
|
|
146
|
+
| 字段 | 默认 | 说明 |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `prefix` | `""` | 服务前缀,如 `PMD_PREFIX` |
|
|
149
|
+
| `serviceId` | 由 prefix 推导 | 流程服务名 |
|
|
150
|
+
| `formCode` / `taBm` / `wfCode` | — | 表单编码 / 主表表名 / 流程编码 |
|
|
151
|
+
| `titleField` | `name` | 页签标题字段,与列表的 `titleField` 保持一致 |
|
|
152
|
+
| `wfEnabled` | `true` | 是否启用流程(`false` 则所有流程按钮都不出) |
|
|
153
|
+
| `saveSubmitEnabled` | `true` | 新增页是否显示「保存并提交流程」 |
|
|
154
|
+
| `wfStartBindSave` | `true` | 点「提交流程」是否先校验并自动保存 |
|
|
155
|
+
| `wfAgreeBindSave` | `true` | 审批点「通过」是否自动保存单据 |
|
|
156
|
+
| `wfEditableFields` | `null` | 流程中仍放开的字段,数组或 `{taskStep: [...]}`;配合模板的嵌套 `el-form`,见下节 |
|
|
157
|
+
| `startParam` | `null` | 启动流程带给流程引擎的业务参数(`/wf/start` 的 `formData`,流程里做条件分支)。对象或函数,函数每次启动时求值 |
|
|
158
|
+
| `hiddenAllButton` | `false` | `true` = 流程面板上所有操作按钮都不出(纯查看态) |
|
|
159
|
+
| `showModifyBtn` | `true` | 是否放出 `wfUtil` 注入的「修改单据」按钮 |
|
|
160
|
+
| `hideWf` | `false` | `true` = 不挂流程信息面板 |
|
|
161
|
+
|
|
162
|
+
另外两个开关 `wfUtil` 只认 `$attrs`,直接写在详情组件标签上:`<bill-detail :tab="tab" default-show-wf-content :to-wf-area.sync="toWfArea" />`(后者是待办进来自动展开流程区)。
|
|
163
|
+
|
|
164
|
+
### 详情页模板的两条硬约束
|
|
165
|
+
|
|
166
|
+
```vue
|
|
167
|
+
<div class="detail-wrap">
|
|
168
|
+
<!-- 只读态:绑 billDisabled,字段整片自动禁用,不用逐个字段绑 -->
|
|
169
|
+
<el-form ref="editForm" :model="billModel" :disabled="billDisabled">
|
|
170
|
+
<el-form> <!-- 空 el-form:隔断 disabled 注入 -->
|
|
171
|
+
<div class="d-header clearfix">
|
|
172
|
+
<div class="fl">标题</div>
|
|
173
|
+
<biz-bill-actions :detail="_self" /> <!-- 组件根节点就是 .fr -->
|
|
174
|
+
</div>
|
|
175
|
+
</el-form>
|
|
176
|
+
<baseTabs> <!-- 必须在 el-form 内 -->
|
|
177
|
+
<baseTabPane :label="$t1('基本信息')">…</baseTabPane>
|
|
178
|
+
</baseTabs>
|
|
179
|
+
</el-form>
|
|
180
|
+
<some-dialog … /> <!-- 弹框放 el-form 之外 -->
|
|
181
|
+
</div>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
1. **`el-form` 必须包住 `baseTabs`**:`wfUtil` 默认把流程面板挂到 `.el-form .d-cont`、侧边导航挂到 `.el-form .slide-wrap`、「修改单据」按钮塞进 `.el-form .d-header > .fr`,结构不对流程信息就出不来。
|
|
185
|
+
2. **整体只读走 `<el-form :disabled="billDisabled">`,头部要套一层空 `<el-form>`**。三条机制凑成的:
|
|
186
|
+
- element 的 `Form` 是 `provide({elForm: this})`,**嵌套一层空 `el-form` 就把外层的 `disabled` 挡住了**,头部按钮不受影响(`ladderPrice`/`stock_check` 那层看着多余的内层 `el-form` 就是干这个的);
|
|
187
|
+
- `el-button` 只在**没有显式传 `disabled`** 时才继承 `elForm.disabled`(`button.vue` 的 `buttonDisabled` 判的是 `$options.propsData.hasOwnProperty('disabled')`),`BizBillActions` 的按钮全部显式传,等于双保险;
|
|
188
|
+
- 流程面板是 `wfUtil` 用 `new Vue().$mount()` 独立挂载后 append 进 DOM 的,**不在组件树里、inject 不到 `elForm`**,所以「通过/驳回/修改单据」本来就不会被带禁。
|
|
189
|
+
|
|
190
|
+
**弹框请声明在 `el-form` 之外**,否则弹框里的按钮会跟着整片禁用。
|
|
191
|
+
|
|
192
|
+
### 流程中要放开个别字段
|
|
193
|
+
|
|
194
|
+
整片禁用之后,给单个输入框传 `:disabled="false"` **是压不回来的**——`input.vue` 的 `inputDisabled` 是 `this.disabled || (this.elForm || {}).disabled`(OR,select / date-picker 同理),只有 `el-button` 是"显式传了就不继承"。所以放开字段得两步:
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
// ① 配置放开哪些字段(billOptions)
|
|
198
|
+
wfEditableFields: ["memo"], // 所有节点
|
|
199
|
+
wfEditableFields: { "2": ["amount"], "*": ["memo"] }, // 按 wfInfo.taskStep 分节点,"*" 兜底
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```vue
|
|
203
|
+
<!-- ② 模板上套一层 el-form 隔断整片禁用,再用 fieldDisabled 决定它自己禁不禁。
|
|
204
|
+
ref="extraForm" + :model 必须带(见下面「必填怎么办」) -->
|
|
205
|
+
<el-form ref="extraForm" :model="billModel">
|
|
206
|
+
<el-form-item prop="memo">
|
|
207
|
+
<el-input v-model="billModel.memo" :disabled="fieldDisabled('memo')" />
|
|
208
|
+
</el-form-item>
|
|
209
|
+
</el-form>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
只配 ① 不写 ② 不会生效——这是故意的:默认永远是关着的,放开必须在模板上显式开口,不会因为白名单配错就整单可改。
|
|
213
|
+
|
|
214
|
+
#### 必填怎么办
|
|
215
|
+
|
|
216
|
+
`el-form-item` 在 `mounted` 里 `dispatch('ElForm', 'el.form.addField')`,只会**向最近的那个 form** 注册;外层 `validate()` 又只遍历自己的 `this.fields`。结论:
|
|
217
|
+
|
|
218
|
+
- **其它字段的必填一点不受影响** —— 外层压根不认识被包起来的那个字段;
|
|
219
|
+
- **被包起来的字段,`prop`/`rules` 默认是空转的** —— 得让内层 form 也校验一遍。
|
|
220
|
+
|
|
221
|
+
所以 `ref="extraForm"` + `:model` 要带上:mixin 的 `validateBill()` 默认校验 `$refs.editForm` **加** `$refs.extraForm`(可以是 `v-for` 出来的数组),各自关掉提示、最后统一只弹一次。
|
|
222
|
+
|
|
223
|
+
⚠️ **内层 form 一定要绑 `:model`**。element 的 `validate()` 开头是 `if (!this.model) { warn; return; }` —— 没绑 model 的 form 调 validate 会直接返回、**callback 永不触发**,塞进 Promise 就把保存链路锁死。`validateBill()` 因此只挑绑了 `model` 的 form(隔断 header 用的那个空 `el-form` 就被自动跳过)。
|
|
224
|
+
|
|
225
|
+
⚠️ **一个详情页有多处内层 form 时,ref 不能同名**。Vue 2 里非 `v-for` 的同名 `ref` 会互相覆盖,只留最后挂载的那个,另一个就漏校了。所以 `getExtraForms()` 是**按 `extraForm` 前缀收集**的:`extraForm` / `extraFormProduct` / `extraFormItems` 都会被收进来(`v-for` 出来的数组也支持)。
|
|
226
|
+
|
|
227
|
+
#### 流程中放开整张子表
|
|
228
|
+
|
|
229
|
+
把 `vxe-grid` 整个包进内层 `el-form` 即可:
|
|
230
|
+
|
|
231
|
+
```vue
|
|
232
|
+
<el-form ref="extraFormProduct" :model="billModel">
|
|
233
|
+
<vxe-grid ref="productGrid" :data="billModel.productItemDTOs" v-bind="productOption" />
|
|
234
|
+
</el-form>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
代价与配套:这张表的行内 `form-item` 全部注册到内层 form(外层 `editForm` 不再认识它们),靠上面的前缀收集把它一起校验;单元格的禁用判据也要从 `billDisabled`(整片只读)换成 `fieldDisabled('productItemDTOs')`,并把这个 key 加进 `wfEditableFields`。demo C 的产品明细就是这么放开的。
|
|
238
|
+
|
|
239
|
+
放开规则要按角色、按数据算而不是死名单,覆盖 `getWfEditableFields()` 返回数组即可。
|
|
240
|
+
|
|
241
|
+
### 子表(表格行内)的必填靠不住,要在 validateBillData 里兜
|
|
242
|
+
|
|
243
|
+
行内 `<el-form-item prop="items.2.qty">` 是会注册到外层 `editForm` 的(`dispatch('ElForm')` 走 `$parent` 链,穿得过 vxe 内部组件;点号路径 element 的 `getPropByPath` 也解析得了),**但只对已渲染的行有效**:
|
|
244
|
+
|
|
245
|
+
`$vxeTableUtil.initVxeTable` 给表格配的是 `scrollY: { gt: 20, oSize: 20 }`,**子表超过 20 行就开虚拟滚动**,屏幕外的行压根没渲染、行内 `form-item` 不存在,外层 form 的 `fields` 里自然也没有它们 → 「看着校验过了,其实漏了半张子表」。分页子表的其它页同理;删行/重排时 `prop` 里的 `rowIndex` 还会短暂串位。
|
|
246
|
+
|
|
247
|
+
所以:**行内 `form-item` 只用来给即时红框提示,可靠性放在 `validateBillData()`**,按数据遍历 `getTableData().fullData`:
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
validateBillData() {
|
|
251
|
+
let rows = this.$refs.itemGrid.getTableData().fullData;
|
|
252
|
+
if (!rows.length) return this.$t1("请至少录入一条明细");
|
|
253
|
+
for (let i = 0; i < rows.length; i++) {
|
|
254
|
+
if (rows[i].qty == null || rows[i].qty === "") {
|
|
255
|
+
return this.$t1(`明细第 ${i + 1} 行「数量」不能为空`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null; // 通过
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 表格里的必填怎么「看得见」:表头星号 + 输入框标红
|
|
263
|
+
|
|
264
|
+
两件事 element 都不会自动做:
|
|
265
|
+
|
|
266
|
+
- **表头星号**:element 的 `*` 是 `el-form-item` 按 `label` 渲染的,表格单元格里没有 label,列头文字是 vxe 的 `title` —— 星号得自己往列头加。
|
|
267
|
+
- **输入框标红**:必须把单元格内容**套进 `el-form-item`,并给一个在 form 的 `model` 下真实可达的 `prop` 路径**,element 才会挂 `is-error`(红框)。裸着写 `<el-input>` 校验多少次都不会红。
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
// options 式列配置(vxe 的 slot 签名是 (params, h),所以 requiredHeader 不用 JSX)
|
|
271
|
+
import { requiredHeader, buildRowProp } from "@base/components/bizTab/billTableUtil";
|
|
272
|
+
|
|
273
|
+
{
|
|
274
|
+
title: this.$t1("阶梯价"), field: "ladderPrice", width: 140,
|
|
275
|
+
slots: {
|
|
276
|
+
header: requiredHeader(this.$t1("阶梯价")),
|
|
277
|
+
default: ({ row, rowIndex }) => [
|
|
278
|
+
<el-form-item
|
|
279
|
+
prop={buildRowProp("productItemDTOs", rowIndex, "ladderPrice")}
|
|
280
|
+
rules={[{ required: true, trigger: ["blur", "change"] }]}
|
|
281
|
+
show-message={false}
|
|
282
|
+
>
|
|
283
|
+
<base-input-number value={row.ladderPrice} onInput={(v) => { row.ladderPrice = v; }} />
|
|
284
|
+
</el-form-item>,
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```vue
|
|
291
|
+
<!-- 模板式 vxe-column 同理 -->
|
|
292
|
+
<vxe-column field="qty" :title="$t1('数量')" width="120">
|
|
293
|
+
<template #header><span><em class="f-red">*</em>{{ $t1("数量") }}</span></template>
|
|
294
|
+
<template #default="{ row, rowIndex }">
|
|
295
|
+
<el-form-item :prop="`items.${rowIndex}.qty`"
|
|
296
|
+
:rules="[{ required: true, trigger: ['blur', 'change'] }]"
|
|
297
|
+
:show-message="false">
|
|
298
|
+
<base-input-number v-model="row.qty" />
|
|
299
|
+
</el-form-item>
|
|
300
|
+
</template>
|
|
301
|
+
</vxe-column>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
三个要点:
|
|
305
|
+
|
|
306
|
+
1. **`prop` 路径必须在 `editForm` 的 `model` 下可达**,所以**子表数据要放进 `billModel` 里**(`billModel.items` / `billModel[taBm]`),别单独挂一个 `data` 字段——挂外面 element 会 warn「please transfer a valid prop path to form item」且校验静默失效。demo B 的子表就是为此放进模型的(stdxx 原页面也是这么写的)。
|
|
307
|
+
2. **`show-message` 关掉**:窄单元格放不下错误文案,行高会被顶乱。红框负责「哪个格子错了」,具体哪行哪个字段由 `validateBillData()` 的提示说清。
|
|
308
|
+
3. **附件、多选的单元格别写 `rules`**:空数组会被 `async-validator` 放行,写了既不红也不拦,纯属误导——这两类一律交给下面的 `checkRowsRequired`。
|
|
309
|
+
|
|
310
|
+
### 附件、多选、"至少一条"这三类必填,`el-form-item` 全兜不住
|
|
311
|
+
|
|
312
|
+
`async-validator` 的 `required` **不指定 `type` 时只判 `undefined` / `null` / `""`,空数组是放行的**。附件、多选(`el-select multiple`)的值都是数组,写了 `required: true` 照样过。加上前面那条虚拟滚动的问题,所以 mixin 直接给了两个现成方法(返回错误文案或 `null`,用 `||` 串起来,第一条不过就中止):
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
validateBillData() {
|
|
316
|
+
return (
|
|
317
|
+
// 表头:附件至少一个、多选至少一项
|
|
318
|
+
this.checkBillRequired([
|
|
319
|
+
{ field: "attachmentDTOs", label: "凭证", type: "attachment" },
|
|
320
|
+
{ field: "areaIds", label: "适用区域", type: "multi" },
|
|
321
|
+
{ field: "ladderName", label: "名称" }, // 省略 type = 标量必填
|
|
322
|
+
])
|
|
323
|
+
// 子表:至少一行 + 逐行必填(行内附件/多选同样支持)
|
|
324
|
+
|| this.checkRowsRequired({
|
|
325
|
+
label: "产品明细",
|
|
326
|
+
rows: this.$refs.productGrid.getTableData().fullData, // 全量,不受虚拟滚动影响
|
|
327
|
+
minRows: 1, // 默认就是 1
|
|
328
|
+
fields: [
|
|
329
|
+
{ field: "productVonderCode", label: "产品编码" },
|
|
330
|
+
{ field: "files", label: "附件", type: "attachment" },
|
|
331
|
+
{ field: "areas", label: "适用区域", type: "multi" },
|
|
332
|
+
// 自定义规则:返回文案即不通过,"第 N 行"前缀自动带上
|
|
333
|
+
{ field: "maxQuantity", check: (val, row) =>
|
|
334
|
+
Number(row.minQuantity) > Number(val) ? "「起始数量」不能大于「截止数量」" : null },
|
|
335
|
+
],
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
判空规则统一在 `billValidators.js`(也可单独 import 用)。附件/多选的值在本仓库有四种形态——数组、单个对象(`baseUpload` 单文件)、逗号串、JSON 串(后台存文本的老字段)——`toList()` 都归一处理,不用各页面自己判。
|
|
342
|
+
|
|
343
|
+
`minRows` 只想校"有没有数据"就不传 `fields`;只想校字段不卡行数就传 `minRows: 0`。
|
|
344
|
+
|
|
345
|
+
demo A(表头多选 + 表头附件 + 行内多选 + 行内附件 + 至少一行,全套)、demo B、demo C 都实现了这个钩子,可直接照抄。
|
|
346
|
+
|
|
347
|
+
demo C(`views/demo/bizTab/wf/detail.vue`)的「备注」就是这么放开的,可以直接照抄。
|
|
348
|
+
|
|
349
|
+
### 菜单接入
|
|
350
|
+
|
|
351
|
+
菜单的组件路径填 `@base/views/demo/bizTab/simple/list` 这种形式(本仓库 `@base` = `src`),路由由后台菜单驱动,不用改 `router/`。本仓库的两条 demo 路由是为了本地免配菜单,放在 `router/modules/customer.js`(`router/index.js` 走 `@/router/modules/customer`,所以消费应用加载的是它自己那份,不会被这两条污染)。
|
|
352
|
+
|
|
353
|
+
## 设计上的几个关键点
|
|
354
|
+
|
|
355
|
+
- **新增页签用合成 id**:`add_<时间戳>_<随机>` 同时当 `tabName` 和 `data.id`,`dataId` 显式置 `null`——不然 x-tabs 默认拿 `row.id` 当 `dataId`,详情会拿假 id 去 `getOne`。保存后**只换 `tab.data.id`,不换 `tabName`**:换了会连带 `el-tab-pane` 重建、详情被迫重挂载,正好破坏下一条。
|
|
356
|
+
- **元数据升级与内容刷新分开**:保存成功先只升级页签(`dataId`/`isAdd`/标题),不重渲染。这样即便随后提交失败或取消候选人弹框,页签已是编辑态,再点提交是更新而不是再新增一张;而详情那唯一一次刷新留给流程结束分支,不刷两遍。
|
|
357
|
+
- **`closeToList` 的顺序**:先切列表,再 `doRemoveTab`。反过来会被 `x-tabs.doRemoveTab` 自己抢走 `activeName`。列表刷新统一收在 `activeName` 的 watcher 里(回到列表就刷),所以手动点 ×、程序化关签、手动点列表页签三条路径都不会留下陈旧列表。
|
|
358
|
+
- **覆盖 `$baseReload`**:wf 组件内部所有刷新都硬编码走 `$baseReload`,在 mixin 里覆盖它是把流程刷新接进页签体系、又不改 `wf.js` 的唯一切口。不在页签里时原样退回默认 `$emit("reload")`。
|
|
359
|
+
- **`_handleCallback` 的时序**:wf 是 `reloadContent()` 先跑、`closeWinOnCheck()` 后跑,也就是先刷一次才轮到告诉我们本次是「通过」还是「驳回」。所以 `$baseReload` 把刷新延后一拍(`setTimeout 0`),`_handleCallback` 里若判定要关页签就把那次刷新撤掉。
|
|
360
|
+
- **通过自动保存分两步**:`onClickAgree` 里校验(意见框弹出前),`onBeforeAgree` 里保存(调 `/wf/submit` 前)。只在后者做会出现「填完意见才报必填」。
|
|
361
|
+
- **启动流程失败/取消后要不要刷详情,看「这次存没存」,不看 `wfStartBindSave` 配了没**:后台没给这个 `wfCode` 配启用的流程单据时,wf 会弹「无法启动,没有流程单据」然后 `close()` → 回调 `onCancel`。这时什么都没发生,若照配置项无条件刷,用户看到的就是详情莫名重载、未保存的编辑还被丢掉。所以用 `_wfStartSaved` 标记本次是否真的存过(`onBeforeStartSubmit` 存成功、或「保存并提交」拉框前置上)。
|
|
362
|
+
- **`billSubmitting` 只在"已入库、待提交"这段置上**:`wfStartDialog` 单模板 + 启动确认框那条路,用户点「取消」时它既不 `close()` 也不回调 `onCancel`(`close` 只挂在 el-dialog 的 `@close` 上,而那种情况 `showDialog` 从没置过 true),所以在拉起弹框时就置提交态会没人来解、保存按钮永久禁用。改在 `onBeforeStartSubmit` 保存成功后置,此后必然走 `onStartSuccess`/`onStartFail`/`onCancel` 三者之一。
|
|
363
|
+
- **`serviceId` 显式传**:`mixins/wf/index.js` 现在是「`wfConfig.serviceId` 优先,没传才按取数 url 首段推导」,`buildWfOption` 总是显式传,所以单据取数与流程不在同一服务时也不会打偏。
|
|
364
|
+
|
|
365
|
+
## 对公共组件的改动
|
|
366
|
+
|
|
367
|
+
- `components/vb-tabs/x-tabs.vue`:新增可选 prop `beforeRemove`,并把原 `removeTab` 的实现拆成 `doRemoveTab`。不传 `beforeRemove` 时行为与改动前完全一致,低代码那套不受影响。程序化关闭页签请调 `doRemoveTab` 以跳过确认。
|
|
368
|
+
- `mixins/wf/index.js`:`serviceId` 改成 `wfConfig.serviceId || urlArr[1]`(原来无条件用 `urlArr[1]`)。全仓库既有调用方都没传过 `serviceId`,行为不变;口径与 `xform/mixins/scriptHttp.js` 一致。
|
|
369
|
+
|
|
370
|
+
## 已知限制
|
|
371
|
+
|
|
372
|
+
- 页签标题在保存成功、以及每次取数成功后同步;编辑中改了标题字段但没保存,标题不会实时跟着变。
|
|
373
|
+
- 翻单序列是打开详情那一刻的列表快照:列表随后重新查询了,已开页签里的「上一单/下一单」还按老顺序走。
|
|
374
|
+
- 一个列表宿主内的所有页签共用一套 `behavior` 配置,做不到按单据类型分别配置。
|
|
375
|
+
- 页签是 `v-show` 语义常驻,开着的详情都在 DOM 里(`maxTabs` 默认 10 就是为此)。
|
|
376
|
+
- `wfUtil` 手工 `new` 的四个实例(流程面板、侧边导航、修改单据按钮、启动按钮)**已经会在宿主销毁时回收**(`$once("hook:beforeDestroy")` → `$destroy` + `removeChild`)。但 `openStartWfDialog` / `openWfModifyDialog` 那两个 append 到 `document.body` 的弹框实例**仍未回收**:每点一次「提交流程」/「修改单据」就在 body 里留一个空 `div` 和一个活实例。要修得改 `wf.js` 的生命周期收口(`skipStartConfirm` 那条路 el-dialog 从没渲染过,拿不到 `close` 事件),另排一轮。
|
|
377
|
+
- 没有 H5 / 移动端分支(低代码那套的 `layoutType === 'H5'`、std 的 `h5edit.vue` 未抽过来)。
|
|
378
|
+
- 不写保存日志(xform 的 `writeSaveLog` 依赖 `reportTemplate`,手写页没有这东西)。
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子表(vxe-table / vxe-grid)里的必填表现工具。
|
|
3
|
+
*
|
|
4
|
+
* 表格里的必填有两件事要自己做,element 都帮不了:
|
|
5
|
+
* 1. **表头星号**:element 的 `*` 是 el-form-item 按 label 渲染的,表格单元格里没有 label,
|
|
6
|
+
* 列头文字是 vxe 的 title,所以星号得自己往列头上加 —— requiredHeader()。
|
|
7
|
+
* 2. **输入框标红**:得把单元格内容套进 el-form-item 并给一个指向 form model 的真实 prop
|
|
8
|
+
* 路径(如 "items.2.qty"),element 才会在校验失败时给它挂 is-error(红框)。
|
|
9
|
+
* 裸着写 <el-input> 是永远不会红的。
|
|
10
|
+
*
|
|
11
|
+
* 可靠性仍然靠 validateBillData()(表格超 20 行会开虚拟滚动,屏外行没有 form-item),
|
|
12
|
+
* 这里两个只解决「看得见」。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 生成带必填星号的列头 slot。
|
|
17
|
+
*
|
|
18
|
+
* vxe 的 options 式列配置里 slot 函数签名是 (params, h),所以不用 JSX 也能建节点。
|
|
19
|
+
*
|
|
20
|
+
* { title: "阶梯价", field: "ladderPrice",
|
|
21
|
+
* slots: { header: requiredHeader("阶梯价"), default: … } }
|
|
22
|
+
*
|
|
23
|
+
* @param {String} title 列标题
|
|
24
|
+
* @returns {Function} vxe 列头 slot
|
|
25
|
+
*/
|
|
26
|
+
export function requiredHeader(title) {
|
|
27
|
+
return function (params, h) {
|
|
28
|
+
return [
|
|
29
|
+
h("span", [h("em", { staticClass: "f-red" }, "*"), title]),
|
|
30
|
+
];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 拼子表单元格的 form-item prop 路径。必须是 form 的 model 下真实可达的路径,
|
|
36
|
+
* 否则 element 会 warn「please transfer a valid prop path to form item」且校验不生效。
|
|
37
|
+
*
|
|
38
|
+
* buildRowProp("productItemDTOs", rowIndex, "ladderPrice")
|
|
39
|
+
* → "productItemDTOs.2.ladderPrice"
|
|
40
|
+
*
|
|
41
|
+
* 注意:路径里的下标是渲染时拼死的,删行/重排后要靠重新渲染才对齐,
|
|
42
|
+
* 所以它只适合做即时提示,别拿它当唯一的校验依据。
|
|
43
|
+
*/
|
|
44
|
+
export function buildRowProp(listField, rowIndex, field) {
|
|
45
|
+
return `${listField}.${rowIndex}.${field}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default { requiredHeader, buildRowProp };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 单据数据级校验工具 —— 给 bizBillDetailMixin 的 validateBillData() 用。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不用 el-form-item 的 required 解决:
|
|
5
|
+
* 1. async-validator 的 required 不指定 type 时只判 undefined / null / "",
|
|
6
|
+
* **空数组是放行的** —— 附件、多选这类集合型字段写了必填也校不出来;
|
|
7
|
+
* 2. 附件/多选在本仓库有四种值形态(数组 / 单个对象 / 逗号串 / JSON 串),
|
|
8
|
+
* 每个页面各写一遍判空必然写歪;
|
|
9
|
+
* 3. 子表行内的 form-item 只对已渲染的行有效(表格 scrollY gt:20 开虚拟滚动),
|
|
10
|
+
* 屏幕外的行根本没注册进 form,必须按数据遍历。
|
|
11
|
+
*
|
|
12
|
+
* 统一返回「错误文案 或 null」,多个检查用 || 串起来即可,第一条不过就中止。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const identity = (str) => str;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 把集合型字段的值归一成数组。覆盖本仓库实际出现的几种形态:
|
|
19
|
+
* [] / [{...}] baseUpload(:file.sync) 的数组、el-select multiple
|
|
20
|
+
* {...} baseUpload 单文件
|
|
21
|
+
* "a.png,b.png" 逗号串
|
|
22
|
+
* "[]" / "[{...}]" JSON 串(后台存文本的老字段)
|
|
23
|
+
*/
|
|
24
|
+
export function toList(val) {
|
|
25
|
+
if (val === null || val === undefined || val === "") return [];
|
|
26
|
+
if (Array.isArray(val)) {
|
|
27
|
+
return val.filter(
|
|
28
|
+
(item) => item !== null && item !== undefined && item !== ""
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (typeof val === "string") {
|
|
32
|
+
let str = val.trim();
|
|
33
|
+
if (!str || str === "[]") return [];
|
|
34
|
+
if (str.charAt(0) === "[" || str.charAt(0) === "{") {
|
|
35
|
+
try {
|
|
36
|
+
let parsed = JSON.parse(str);
|
|
37
|
+
if (Array.isArray(parsed)) return toList(parsed);
|
|
38
|
+
return parsed ? [parsed] : [];
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// 不是合法 JSON,当逗号串处理
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return str.split(",").filter((item) => item !== "");
|
|
44
|
+
}
|
|
45
|
+
if (typeof val === "object") {
|
|
46
|
+
return Object.keys(val).length ? [val] : [];
|
|
47
|
+
}
|
|
48
|
+
return [val];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 标量必填:空串、纯空格、null、undefined、空数组都算空
|
|
52
|
+
export function isBlank(val) {
|
|
53
|
+
if (val === null || val === undefined) return true;
|
|
54
|
+
if (typeof val === "string") return val.trim() === "";
|
|
55
|
+
if (Array.isArray(val)) return val.length === 0;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 集合必填(附件、多选)
|
|
60
|
+
export function isEmptyList(val) {
|
|
61
|
+
return toList(val).length === 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 按字段清单校验一个对象(主表或子表某一行)。
|
|
66
|
+
*
|
|
67
|
+
* @param {Object} target 被校验的对象
|
|
68
|
+
* @param {Array} fields [{ field, label, type, check }]
|
|
69
|
+
* type: 省略/"text" 标量必填 | "attachment" 附件至少一个 | "multi" 多选至少一项
|
|
70
|
+
* check(value, target) 自定义校验,返回错误文案即不通过(优先于 type)
|
|
71
|
+
* @param {Object} options { prefix 文案前缀(如"产品明细第 3 行"), t 翻译函数 }
|
|
72
|
+
* @returns {String|null} 第一条错误文案,全通过返回 null
|
|
73
|
+
*/
|
|
74
|
+
export function checkRequired(target, fields, options) {
|
|
75
|
+
let opts = options || {};
|
|
76
|
+
let t = opts.t || identity;
|
|
77
|
+
let prefix = opts.prefix || "";
|
|
78
|
+
let row = target || {};
|
|
79
|
+
let list = fields || [];
|
|
80
|
+
for (let i = 0; i < list.length; i++) {
|
|
81
|
+
let item = list[i];
|
|
82
|
+
if (!item || !item.field) continue;
|
|
83
|
+
let value = row[item.field];
|
|
84
|
+
let label = item.label || item.field;
|
|
85
|
+
if (typeof item.check === "function") {
|
|
86
|
+
let msg = item.check(value, row);
|
|
87
|
+
if (msg) return prefix ? prefix + msg : msg;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let type = item.type || "text";
|
|
91
|
+
if (type === "attachment") {
|
|
92
|
+
if (isEmptyList(value)) {
|
|
93
|
+
return prefix + t(`「${label}」至少上传一个附件`);
|
|
94
|
+
}
|
|
95
|
+
} else if (type === "multi" || type === "list") {
|
|
96
|
+
if (isEmptyList(value)) {
|
|
97
|
+
return prefix + t(`「${label}」至少选择一项`);
|
|
98
|
+
}
|
|
99
|
+
} else if (isBlank(value)) {
|
|
100
|
+
return prefix + t(`「${label}」不能为空`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 校验子表:先卡行数,再逐行按字段清单校验。
|
|
108
|
+
*
|
|
109
|
+
* 行数据请传 grid.getTableData().fullData(全量,不受虚拟滚动/分页影响),
|
|
110
|
+
* 别传当前渲染的行。
|
|
111
|
+
*
|
|
112
|
+
* @param {Object} config { rows, label, minRows = 1, fields, t }
|
|
113
|
+
* @returns {String|null}
|
|
114
|
+
*/
|
|
115
|
+
export function checkRows(config) {
|
|
116
|
+
let cfg = config || {};
|
|
117
|
+
let t = cfg.t || identity;
|
|
118
|
+
let label = cfg.label || t("明细");
|
|
119
|
+
let rows = cfg.rows || [];
|
|
120
|
+
let minRows = cfg.minRows === undefined ? 1 : cfg.minRows;
|
|
121
|
+
if (rows.length < minRows) {
|
|
122
|
+
return minRows === 1
|
|
123
|
+
? t(`请至少录入一条${label}`)
|
|
124
|
+
: t(`${label}至少需要 ${minRows} 条`);
|
|
125
|
+
}
|
|
126
|
+
for (let i = 0; i < rows.length; i++) {
|
|
127
|
+
let msg = checkRequired(rows[i], cfg.fields, {
|
|
128
|
+
t,
|
|
129
|
+
prefix: t(`${label}第 ${i + 1} 行`),
|
|
130
|
+
});
|
|
131
|
+
if (msg) return msg;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default { toList, isBlank, isEmptyList, checkRequired, checkRows };
|