From 2d401c923de511def2248d98a7edfbaa03649253 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Fri, 21 Aug 2026 17:01:39 +0800 Subject: [PATCH] =?UTF-8?q?v3.9.5=E7=89=88=E6=9C=AC=E5=8F=91=E5=B8=83=20vu?= =?UTF-8?q?e3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jeecgboot-vue3/.claudeignore | 2 + jeecgboot-vue3/.env.development | 2 +- jeecgboot-vue3/.env.prod_electron | 2 +- jeecgboot-vue3/.env.production | 2 +- jeecgboot-vue3/.ignore | 59 ++ jeecgboot-vue3/AGENTS.md | 158 ++++ jeecgboot-vue3/build/config/themeConfig.ts | 4 +- jeecgboot-vue3/build/vite/plugin/index.ts | 12 +- jeecgboot-vue3/package.json | 7 +- jeecgboot-vue3/public/resource/js/iconfont.js | 2 +- jeecgboot-vue3/src/api/sys/model/userModel.ts | 2 + jeecgboot-vue3/src/api/sys/upload.ts | 8 +- .../src/components/Basic/src/BasicHelp.vue | 5 + .../src/components/Form/src/componentMap.ts | 2 + .../Form/src/components/ApiSelect.vue | 41 +- .../Form/src/components/FormItem.vue | 12 +- .../src/components/Form/src/helper.ts | 5 + .../src/jeecg/components/JAreaLinkage.vue | 12 +- .../src/jeecg/components/JAuthRuleValue.vue | 230 ++++++ .../Form/src/jeecg/components/JCascader.vue | 74 +- .../Form/src/jeecg/components/JCodeEditor.vue | 15 +- .../src/jeecg/components/JImportModal.vue | 346 +++++++- .../Form/src/jeecg/components/JInput.vue | 5 +- .../src/jeecg/components/JLeftEllipsis.vue | 138 ++++ .../JLinkTableCard/JLinkTableCard.vue | 6 +- .../components/AllTableListModal.vue | 528 ++++++++++++ .../components/LinkTableListModal.vue | 777 ++++++++++++------ .../JLinkTableCard/hooks/useLinkTable.ts | 77 +- .../JLinkTableCard/hooks/useTableColumns.ts | 648 +++++++++++++++ .../jeecg/components/JMultiTextTagInput.vue | 298 +++++++ .../Form/src/jeecg/components/JPopup.vue | 109 ++- .../Form/src/jeecg/components/JPopupDict.vue | 75 +- .../Form/src/jeecg/components/JRangeDate.vue | 37 +- .../src/jeecg/components/JRangeNumber.vue | 10 +- .../src/jeecg/components/JSelectSingle.vue | 232 ++++-- .../Form/src/jeecg/components/JSelectUser.vue | 7 +- .../Form/src/jeecg/components/JTreeSelect.vue | 21 +- .../src/jeecg/components/JUpload/JUpload.vue | 102 ++- .../jeecg/components/JUpload/JUploadModal.vue | 61 +- .../src/jeecg/components/base/JSelectBiz.vue | 203 ++++- .../components/modal/JPopupOnlReportModal.vue | 2 +- .../modal/JSelectUserByDepartmentModal.vue | 36 +- .../components/modal/RoleSelectModal.vue | 1 + .../components/modal/UserSelectByDepModal.vue | 1 + .../components/modal/UserSelectModal.vue | 15 +- .../components/roleSelect/RoleSelectModal.vue | 369 ++++++--- .../components/userSelect/UserListAndRole.vue | 8 +- .../components/userSelect/UserSelectModal.vue | 13 + .../Form/src/jeecg/hooks/useSelectBiz.ts | 9 +- .../src/components/Form/src/types/form.ts | 2 + .../src/components/Form/src/types/index.ts | 5 +- .../src/components/Icon/src/Icon.vue | 18 +- .../InFilter/DatePickerInFilter.vue | 30 +- .../src/components/JVxeCustom/index.ts | 4 + .../src/components/JVxeFileCell.vue | 155 +++- .../src/components/JVxeImageCell.vue | 4 +- .../src/components/JVxeLinkTableCell.vue | 184 +++-- .../src/components/JVxePopupDictCell.vue | 138 ++++ .../JVxeCustom/src/hooks/useFileCell.ts | 36 +- .../Markdown/src/MarkdownViewer.vue | 3 +- .../src/components/Modal/src/BasicModal.vue | 9 +- .../Modal/src/components/ModalWrapper.vue | 20 +- .../components/Scrollbar/src/Scrollbar.vue | 2 +- .../src/components/Tinymce/src/Editor.vue | 37 +- .../src/components/Tinymce/src/tinymce.ts | 6 +- .../src/components/Upload/src/UploadModal.vue | 12 +- .../jeecg/JTabsSelectUser/SelectUserModal.vue | 1 + .../component/PostRankRelation.vue | 37 +- .../jeecg/JTabsSelectUser/useSelectUser.ts | 2 +- .../JVxeTable/src/components/JVxeToolbar.vue | 3 +- .../src/components/cells/JVxeDateCell.vue | 5 +- .../src/components/cells/JVxeSelectCell.vue | 47 +- .../src/components/cells/JVxeUploadCell.vue | 2 +- .../src/hooks/cells/useJVxeUploadCell.ts | 12 +- .../jeecg/JVxeTable/src/hooks/useColumns.ts | 4 +- .../JVxeTable/src/hooks/useJVxeComponent.ts | 22 + .../jeecg/JVxeTable/src/hooks/useToolbar.ts | 1 + .../JVxeTable/src/hooks/useValidateRules.ts | 21 +- .../components/jeecg/JVxeTable/src/install.ts | 9 +- .../jeecg/JVxeTable/src/style/index.less | 19 + .../jeecg/JVxeTable/src/types/JVxeTypes.ts | 3 + .../jeecg/OnLine/SearchFormItem.vue | 18 +- .../jeecg/OnLine/hooks/usePopBiz.ts | 21 +- .../components/jeecg/comment/CommentList.vue | 31 +- .../components/jeecg/comment/DataLogList.vue | 7 +- .../components/jeecg/comment/MyComment.vue | 15 +- .../components/jeecg/comment/UploadChunk.vue | 7 +- .../components/jeecg/comment/useComment.ts | 5 +- .../src/components/wordtpl/DocDesign.vue | 10 +- .../src/hooks/system/useThirdLogin.ts | 3 +- jeecgboot-vue3/src/hooks/web/useDragNotice.ts | 22 +- jeecgboot-vue3/src/hooks/web/useSso.ts | 16 +- .../components/notify/ChangePasswordModal.vue | 79 +- .../components/user-dropdown/DepartSelect.vue | 57 +- .../user-dropdown/UpdatePassword.vue | 9 +- .../header/components/user-dropdown/index.vue | 4 +- jeecgboot-vue3/src/locales/lang/en/common.ts | 1 + .../src/locales/lang/zh-CN/common.ts | 1 + .../src/router/helper/routeHelper.ts | 2 +- jeecgboot-vue3/src/router/index.ts | 17 +- jeecgboot-vue3/src/router/routes/index.ts | 56 +- .../src/store/modules/multipleTab.ts | 25 +- jeecgboot-vue3/src/store/modules/user.ts | 18 +- jeecgboot-vue3/src/utils/common/compUtils.ts | 92 ++- .../src/utils/common/renderUtils.ts | 14 +- jeecgboot-vue3/src/utils/helper/validator.ts | 97 +++ jeecgboot-vue3/src/utils/htmlSanitizer.ts | 124 +++ jeecgboot-vue3/src/utils/http/axios/Axios.ts | 7 +- jeecgboot-vue3/src/utils/index.ts | 119 ++- jeecgboot-vue3/src/utils/propTypes.ts | 6 +- jeecgboot-vue3/src/utils/tinymceGlobalShim.ts | 17 + .../demo/document/form/BasicFormLayout.vue | 2 +- .../demo/document/form/BasicFormModal.vue | 8 +- .../demo/document/form/exampleCustom.data.ts | 4 +- .../src/views/demo/example/example01.vue | 273 ++++++ .../views/demo/example/useBigScreenBridge.ts | 59 ++ .../src/views/demo/href-demo/index.vue | 96 +++ .../demo/jeecg/JVxeTableDemo/JVxeDemo1.vue | 15 + .../views/demo/jeecg/jeecgComponents.data.ts | 45 +- .../src/views/monitor/datalog/datalog.data.ts | 6 +- .../monitor/datasource/DataSourceModal.vue | 17 +- .../monitor/datasource/datasource.data.ts | 9 +- .../src/views/monitor/datasource/index.vue | 10 +- .../src/views/monitor/mynews/DetailModal.vue | 292 +++++-- .../src/views/monitor/mynews/XssWhiteList.ts | 4 + .../src/views/monitor/quartz/quartz.data.ts | 10 +- .../src/views/openapi/OpenApi.data.ts | 95 +-- .../src/views/openapi/OpenApiAuthList.vue | 9 +- .../src/views/openapi/OpenApiList.vue | 14 +- .../src/views/openapi/OpenApiLog.api.ts | 33 + .../src/views/openapi/OpenApiLog.data.ts | 79 ++ .../src/views/openapi/OpenApiLogList.vue | 118 +++ .../views/openapi/components/AuthDrawer.vue | 1 + .../openapi/components/OpenApiAuthDrawer.vue | 7 +- .../openapi/components/OpenApiDrawer.vue | 124 ++- .../components/OpenApiLogDetailModal.vue | 80 ++ .../views/openapi/components/OpenApiModal.vue | 22 +- .../src/views/super/airag/aiapp/AiApp.api.ts | 13 +- .../src/views/super/airag/aiapp/AiAppList.vue | 216 +++-- .../views/super/airag/aiapp/chat/AiChat.vue | 53 +- .../super/airag/aiapp/chat/ThinkText.vue | 5 +- .../src/views/super/airag/aiapp/chat/chat.vue | 191 +++-- .../views/super/airag/aiapp/chat/chatError.ts | 23 + .../super/airag/aiapp/chat/chatMessage.vue | 59 +- .../views/super/airag/aiapp/chat/chatText.vue | 150 +++- .../aiapp/chat/components/CardTemplate.vue | 11 +- .../aiapp/chat/components/ImageViewer.vue | 1 + .../jeecg-tags/tool-exec/JeecgToolExec.vue | 6 +- .../views/super/airag/aiapp/chat/js/chat.js | 3 +- .../super/airag/aiapp/chat/presetQuestion.vue | 2 +- .../views/super/airag/aiapp/chat/slide.vue | 4 + .../super/airag/aiapp/chat/style/style.less | 19 +- .../aiapp/components/AiAppAddFlowModal.vue | 123 +-- .../components/AiAppAddKnowledgeModal.vue | 6 +- .../aiapp/components/AiAppAddMcpModal.vue | 4 +- .../components/AiAppGeneratedPromptModal.vue | 31 +- .../airag/aiapp/components/AiAppModal.vue | 2 +- .../components/AiAppPromptMarketModal.vue | 20 +- .../components/AiAppQuickCommandModal.vue | 2 +- .../airag/aiapp/components/AiAppSendModal.vue | 32 +- .../aiapp/components/AiAppSettingModal.vue | 99 ++- .../aiapp/components/AiUserVariablesModal.vue | 1 + .../super/airag/aicloth/AiClothChange.vue | 31 +- .../airag/aiknowledge/AiKnowledgeBaseList.vue | 22 +- .../views/super/airag/aimcp/AiragMcp.api.ts | 8 +- .../views/super/airag/aimcp/AiragMcpList.vue | 97 ++- .../aimcp/components/AiragMcpDetailModal.vue | 1 + .../views/super/airag/aimodel/AiModelList.vue | 47 +- .../airag/aimodel/components/AiModelModal.vue | 26 +- .../super/airag/aimodel/components/model.json | 141 +++- .../super/airag/aimodel/icon/imstdio.png | Bin 43709 -> 12829 bytes .../views/super/airag/aimodel/icon/kimi.ico | Bin 0 -> 15086 bytes .../super/airag/aimodel/icon/minimax.png | Bin 0 -> 10635 bytes .../views/super/airag/aimodel/icon/vllm.png | Bin 89976 -> 5199 bytes .../super/airag/aimodel/icon/volcengine.png | Bin 0 -> 3251 bytes .../views/super/airag/aimodel/model.api.ts | 12 + .../views/super/airag/aimodel/model.data.ts | 27 +- .../views/super/airag/aiposter/AiPainting.vue | 18 +- .../super/airag/aiposter/AiPoster.data.ts | 14 + .../airag/aiprompts/AiragExtData.data.ts | 2 +- .../airag/aiprompts/AiragExtDataList.vue | 22 +- .../super/airag/aiprompts/AiragPrompts.api.ts | 23 + .../airag/aiprompts/AiragPrompts.data.ts | 84 +- .../airag/aiprompts/AiragPromptsList.vue | 8 +- .../components/AiEvaluatorDebugModal.vue | 7 +- .../components/AiEvaluatorSettingModal.vue | 4 +- .../components/AiPromptSettingModal.vue | 6 +- .../components/AiragDataSetModal.vue | 19 +- .../AiragPromptsRecycleBinModal.vue | 164 ++++ .../views/super/airag/aiwriter/AiWriter.vue | 13 +- .../super/airag/aiwriter/AiWriterLeft.vue | 4 +- .../ocr/components/AiOcrAnalysisModal.vue | 14 +- .../airag/wordtpl/EoaWordTemplate.data.ts | 4 +- .../airag/wordtpl/EoaWordTemplateList.vue | 26 +- .../cgform/auto/comp/JOnlineSearchSelect.vue | 12 +- .../online/cgform/auto/comp/OnlineForm.vue | 143 ++-- .../cgform/auto/comp/OnlineFormDetail.vue | 12 +- .../online/cgform/auto/comp/OnlinePopForm.vue | 140 +++- .../cgform/auto/comp/OnlinePopListModal.vue | 21 +- .../cgform/auto/comp/OnlinePopModal.vue | 12 +- .../cgform/auto/comp/OnlineQueryForm.vue | 203 +++-- .../online/cgform/auto/comp/OnlineSubForm.vue | 54 +- .../cgform/auto/comp/OnlineSubFormDetail.vue | 14 +- .../cgform/auto/comp/TaskOpinionList.vue | 177 ++++ .../auto/comp/factory/FormSchemaFactory.ts | 5 + .../cgform/auto/comp/factory/IFormSchema.ts | 5 +- .../impl/CategoryLinkageForQueryWidget.ts | 17 + .../auto/comp/factory/impl/LinkTableWidget.ts | 3 + .../auto/comp/factory/impl/NumberWidget.ts | 3 + .../auto/comp/factory/impl/SelectWidget.ts | 22 +- .../comp/factory/impl/TreeSelectWidget.ts | 4 + .../auto/comp/superquery/SuperQuery.vue | 79 +- .../superquery/SuperQueryValComponent.vue | 10 +- .../auto/comp/superquery/useSuperQuery.ts | 9 +- .../cgform/auto/default/OnlineAutoList.vue | 21 +- .../cgform/auto/default/OnlineAutoModal.vue | 2 +- .../cgform/auto/default/OnlineDetailModal.vue | 36 +- .../cgform/auto/erp/OnlCgformErpList.vue | 45 +- .../cgform/auto/erp/OnlCgformErpSubTable.vue | 29 +- .../innerTable/OnlCgformInnerSubTable.vue | 47 +- .../innerTable/OnlCgformInnerTableList.vue | 60 +- .../cgform/auto/tab/OnlCgformTabList.vue | 20 +- .../auto/tab/modal/OnlineTabAutoModal.vue | 4 +- .../auto/tab/modal/OnlineTabDetailModal.vue | 4 +- .../cgform/auto/tab/modal/OnlineTabForm.vue | 46 +- .../cgform/auto/tree/OnlineAutoTreeList.vue | 61 +- .../views/super/online/cgform/cgform.data.ts | 7 + .../cgform/components/CgformHeadForm.vue | 56 +- .../online/cgform/components/CgformModal.vue | 78 +- .../cgform/components/DbToOnlineModal.vue | 2 +- .../cgform/components/ExtendConfigModal.vue | 33 +- .../components/auth/AuthManagerDrawer.vue | 14 +- .../components/auth/AuthSetterModal.vue | 26 + .../online/cgform/components/auth/auth.api.ts | 3 + .../cgform/components/auth/auth.data.ts | 492 +++++++++-- .../auth/manager/AuthButtonConfig.vue | 78 +- .../auth/manager/AuthDataConfig.vue | 489 ++++++++++- .../auth/manager/AuthFieldConfig.vue | 98 ++- .../components/auth/setter/AuthButtonTree.vue | 14 + .../components/auth/setter/AuthDataTree.vue | 14 + .../components/auth/setter/LeftDepart.vue | 9 +- .../components/auth/setter/LeftUser.vue | 3 + .../components/button/BuiltInButtonList.vue | 3 +- .../components/enhance/EnhanceJavaModal.vue | 14 +- .../components/enhance/EnhanceJsHistory.vue | 68 +- .../components/enhance/EnhanceJsModal.vue | 15 +- .../components/enhance/EnhanceSqlModal.vue | 3 + .../cgform/components/enhance/enhance.data.ts | 44 +- .../components/tables/CheckDictTable.vue | 4 +- .../components/tables/DBAttributeTable.vue | 16 +- .../cgform/components/tables/IndexTable.vue | 1 + .../components/tables/PageAttributeTable.vue | 350 ++++++-- .../cgform/components/tables/QueryTable.vue | 4 + .../components/tables/components/aiModal.vue | 2 +- .../online/cgform/extend/DictConfigModal.vue | 163 ++++ .../cgform/extend/FieldExtendJsonModal.vue | 43 +- .../online/cgform/extend/form/DetailForm.vue | 31 +- .../cgform/extend/form/useDetailForm.ts | 13 +- .../extend/linkTable/LinkTableInput.vue | 35 +- .../cgform/extend/linkTable/useLinkTable.ts | 107 ++- .../cgform/hooks/aitest/useOnlineTest.ts | 2 +- .../online/cgform/hooks/auto/useAutoForm.ts | 51 +- .../online/cgform/hooks/auto/useListButton.ts | 41 +- .../hooks/auto/useOnlineTableContext.ts | 39 +- .../cgform/hooks/auto/useTableColumns.ts | 35 +- .../online/cgform/hooks/useCgformList.ts | 25 +- .../super/online/cgform/hooks/useGuide.ts | 1 + .../super/online/cgform/hooks/useSchemas.ts | 163 +++- .../online/cgform/router/cgformRouter.ts | 10 +- .../cgform/share/components/SingleView.vue | 4 +- .../views/super/online/cgform/types/index.ts | 2 + .../super/online/cgform/util/FieldDefVal.ts | 86 +- .../super/online/cgreport/cgreport.api.ts | 11 + .../cgreport/components/CgreportModal.vue | 42 +- .../src/views/super/online/cgreport/index.vue | 31 +- .../online/graphreport/GraphreportList.vue | 32 +- .../graphreport/auto/GraphreportAutoChart.vue | 38 +- .../graphreport/auto/hooks/useChartRender.ts | 43 +- .../auto/hooks/useParseFormSchemas.ts | 32 +- .../components/GraphreportModal.vue | 4 +- .../online/graphreport/graphreport.api.ts | 10 + jeecgboot-vue3/src/views/sys/iframe/index.vue | 4 +- .../src/views/sys/login/OAuth2Login.vue | 9 +- .../src/views/sys/login/useLogin.ts | 2 +- .../src/views/system/address/index.vue | 23 +- .../views/system/appconfig/ThirdApp.api.ts | 12 +- .../views/system/appconfig/ThirdApp.data.ts | 4 + .../ThirdAppBindWeEnterpriseModal.vue | 1 + .../system/appconfig/ThirdAppConfigList.vue | 10 + .../system/appconfig/ThirdAppConfigModal.vue | 4 +- .../appconfig/ThirdAppFeishuConfigForm.vue | 303 +++++++ .../views/system/category/category.data.ts | 2 +- .../views/system/checkRule/check.rule.data.ts | 4 +- .../depart/components/DepartFormTab.vue | 1 + .../depart/components/DepartLeftTree.vue | 72 +- .../depart/components/DepartUserList.vue | 22 +- .../src/views/system/depart/depart.api.ts | 6 + .../src/views/system/depart/index.vue | 13 +- .../components/DepartBaseInfoTab.vue | 11 +- .../departUser/components/DepartTree.vue | 103 ++- .../components/DepartUserInfoTab.vue | 4 +- .../system/departUser/depart.user.api.ts | 6 + .../system/dict/components/DictItemList.vue | 38 +- .../dict/components/DictRecycleBinModal.vue | 30 +- .../src/views/system/dict/index.vue | 10 +- .../views/system/fillRule/FillRuleModal.vue | 13 +- .../src/views/system/homeConfig/home.api.ts | 9 + .../src/views/system/homeConfig/index.vue | 24 +- .../views/system/loginmini/OAuth2Login.vue | 9 +- .../src/views/system/menu/DataRuleList.vue | 3 +- .../src/views/system/menu/index.vue | 16 +- .../src/views/system/menu/menu.data.ts | 4 +- .../message/components/SysMessageList.vue | 2 +- .../message/components/SysMessageModal.vue | 9 +- .../system/message/template/TemplateModal.vue | 2 +- .../src/views/system/notice/NoticeModal.vue | 4 +- .../src/views/system/notice/index.vue | 23 +- .../src/views/system/notice/notice.api.ts | 2 +- .../system/onlineuser/OnlineUser.data.ts | 6 + .../views/system/position/PositionModal.vue | 4 +- .../src/views/system/position/index.vue | 176 +++- .../views/system/position/position.data.ts | 17 +- .../src/views/system/role/TenantRoleList.vue | 4 +- .../role/components/RolePermissionDrawer.vue | 96 ++- .../system/role/components/RoleUserTable.vue | 4 +- .../src/views/system/role/role.data.ts | 40 + .../tableWhiteList/SysTableWhiteList.data.ts | 6 +- .../components/TenantRecycleBinModal.vue | 2 +- .../tenant/components/TenantUserList.vue | 1 + .../src/views/system/ugroup/SysUgroupList.vue | 9 + .../src/views/system/ugroup/ugroup.api.ts | 2 +- .../src/views/system/user/UserDrawer.vue | 33 +- .../src/views/system/user/index.vue | 6 + .../src/views/system/user/user.data.ts | 27 +- jeecgboot-vue3/tsconfig.json | 2 +- jeecgboot-vue3/vite.config.ts | 62 +- 336 files changed, 13150 insertions(+), 2410 deletions(-) create mode 100644 jeecgboot-vue3/.ignore create mode 100644 jeecgboot-vue3/AGENTS.md create mode 100644 jeecgboot-vue3/src/components/Form/src/jeecg/components/JAuthRuleValue.vue create mode 100644 jeecgboot-vue3/src/components/Form/src/jeecg/components/JLeftEllipsis.vue create mode 100644 jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/AllTableListModal.vue create mode 100644 jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useTableColumns.ts create mode 100644 jeecgboot-vue3/src/components/Form/src/jeecg/components/JMultiTextTagInput.vue create mode 100644 jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxePopupDictCell.vue create mode 100644 jeecgboot-vue3/src/utils/htmlSanitizer.ts create mode 100644 jeecgboot-vue3/src/utils/tinymceGlobalShim.ts create mode 100644 jeecgboot-vue3/src/views/demo/example/example01.vue create mode 100644 jeecgboot-vue3/src/views/demo/example/useBigScreenBridge.ts create mode 100644 jeecgboot-vue3/src/views/demo/href-demo/index.vue create mode 100644 jeecgboot-vue3/src/views/openapi/OpenApiLog.api.ts create mode 100644 jeecgboot-vue3/src/views/openapi/OpenApiLog.data.ts create mode 100644 jeecgboot-vue3/src/views/openapi/OpenApiLogList.vue create mode 100644 jeecgboot-vue3/src/views/openapi/components/OpenApiLogDetailModal.vue create mode 100644 jeecgboot-vue3/src/views/super/airag/aiapp/chat/chatError.ts create mode 100644 jeecgboot-vue3/src/views/super/airag/aimodel/icon/kimi.ico create mode 100644 jeecgboot-vue3/src/views/super/airag/aimodel/icon/minimax.png create mode 100644 jeecgboot-vue3/src/views/super/airag/aimodel/icon/volcengine.png create mode 100644 jeecgboot-vue3/src/views/super/airag/aiprompts/components/AiragPromptsRecycleBinModal.vue create mode 100644 jeecgboot-vue3/src/views/super/online/cgform/auto/comp/TaskOpinionList.vue create mode 100644 jeecgboot-vue3/src/views/super/online/cgform/auto/comp/factory/impl/CategoryLinkageForQueryWidget.ts create mode 100644 jeecgboot-vue3/src/views/super/online/cgform/extend/DictConfigModal.vue create mode 100644 jeecgboot-vue3/src/views/system/appconfig/ThirdAppFeishuConfigForm.vue diff --git a/jeecgboot-vue3/.claudeignore b/jeecgboot-vue3/.claudeignore index dd1e0e9f0..67a31a47a 100644 --- a/jeecgboot-vue3/.claudeignore +++ b/jeecgboot-vue3/.claudeignore @@ -12,6 +12,7 @@ *.iws *.ipr out/ +.playwright-mcp # Eclipse .classpath @@ -38,6 +39,7 @@ logs/ # Node (frontend artifacts if any) node_modules/ dist/ +mock/ # Docker volumes / data docker/data/ diff --git a/jeecgboot-vue3/.env.development b/jeecgboot-vue3/.env.development index 97118e296..5628ec637 100644 --- a/jeecgboot-vue3/.env.development +++ b/jeecgboot-vue3/.env.development @@ -29,7 +29,7 @@ VITE_APP_SUB_jeecg-app-1 = '//localhost:8092' # 全局隐藏哪些布局。可选属性:sider,header,multi-tabs;多个用逗号隔开 #VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs -# 在线文档编辑版本。可选属性:wps, offlineWps(离线版) ,onlyoffice +# 在线文档编辑版本。可选属性:wps, newWps(新版本wps), offlineWps(离线版) ,onlyoffice VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps # icon组件的iconify图标库使用在线还是本地。可选属性:online, local diff --git a/jeecgboot-vue3/.env.prod_electron b/jeecgboot-vue3/.env.prod_electron index ca0133578..82859d070 100644 --- a/jeecgboot-vue3/.env.prod_electron +++ b/jeecgboot-vue3/.env.prod_electron @@ -11,7 +11,7 @@ VITE_GLOB_DOMAIN_URL=https://api3.boot.jeecg.com # 接口父路径前缀 VITE_GLOB_API_URL_PREFIX= -# 在线文档编辑版本。可选属性:wps, offlineWps(离线版), onlyoffice +# 在线文档编辑版本。可选属性:wps, newWps(新版本wps), offlineWps(离线版), onlyoffice VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps # 全局隐藏哪些布局。可选属性:sider,header,multi-tabs;多个用逗号隔开 diff --git a/jeecgboot-vue3/.env.production b/jeecgboot-vue3/.env.production index ddb4d4bb5..04ad61dc3 100644 --- a/jeecgboot-vue3/.env.production +++ b/jeecgboot-vue3/.env.production @@ -30,7 +30,7 @@ VITE_GLOB_API_URL_PREFIX= # 全局隐藏哪些布局。可选属性:sider,header,multi-tabs;多个用逗号隔开 #VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs -# 在线文档编辑版本。可选属性:wps, offlineWps(离线版), onlyoffice +# 在线文档编辑版本。可选属性:wps, newWps(新版本wps), offlineWps(离线版), onlyoffice VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps # icon组件的iconify图标库使用在线还是本地。可选属性:online, local diff --git a/jeecgboot-vue3/.ignore b/jeecgboot-vue3/.ignore new file mode 100644 index 000000000..67a31a47a --- /dev/null +++ b/jeecgboot-vue3/.ignore @@ -0,0 +1,59 @@ +# Git +.git/ +.gitignore +.gitmodules + +# SVN +.svn/ + +# IntelliJ IDEA +.idea/ +*.iml +*.iws +*.ipr +out/ +.playwright-mcp + +# Eclipse +.classpath +.project +.settings/ + +# VS Code +.vscode/ + +# Maven / Gradle build output +target/ +build/ +!.mvn/wrapper/maven-wrapper.jar + +# OS files +.DS_Store +Thumbs.db +desktop.ini + +# Logs +*.log +logs/ + +# Node (frontend artifacts if any) +node_modules/ +dist/ +mock/ + +# Docker volumes / data +docker/data/ + +# Compiled classes +*.class + +# Custom +*.qqy +代码修改.log +代码修改日志 +*.zip +backup/ +.history/ +.cursor/ +doc/ +docs/ diff --git a/jeecgboot-vue3/AGENTS.md b/jeecgboot-vue3/AGENTS.md new file mode 100644 index 000000000..46c7f1b05 --- /dev/null +++ b/jeecgboot-vue3/AGENTS.md @@ -0,0 +1,158 @@ +# AGENTS.md + +This file provides repository guidance for Codex when working in this project. + +## Communication + +- Always answer in Simplified Chinese first, unless the user explicitly requests another language. + +## Project Overview + +JeecgBoot Vue3 (v3.8.3) is an enterprise low-code admin platform frontend. It is built on Vue 3, TypeScript, Vite, Ant Design Vue 4, and Pinia. It was originally forked from Vben Admin and customized with JeecgBoot-specific components such as online forms, code generation, workflow, and related low-code modules. + +This workspace uses SVN for version control. Do not assume Git is available in this directory. + +Backend: Spring Boot (JeecgBoot), expected at `http://127.0.0.1:8080/jeecg-boot` during development. + +## Related Projects + +| Project | Path | +| --- | --- | +| Frontend (Vue3, this repo) | `E:\workspace-cc-jeecg\jeecgboot-vue3-2026` | +| Backend (Spring Boot 3) | `E:\workspace-cc-jeecg\jeecg-boot-framework-2026` | + +## Commands + +```bash +pnpm install +pnpm dev +pnpm build +pnpm batch:prettier +``` + +- `pnpm dev` starts the dev server on port 3100. +- `pnpm build` creates the production build in `dist/`. +- `pnpm batch:prettier` formats all matching files under `src/`; run it only when broad formatting is intended. +- No regular test script is configured in `package.json`; a Jest config exists, but there is no `test` script. + +## Path Aliases + +- `/@/` or `@/` maps to `src/`. +- `/#/` or `#/` maps to `types/`. +- Prefer the `/@/` prefix with the leading slash, which is the convention used throughout this codebase. + +## Source Structure + +| Directory | Purpose | +| --- | --- | +| `src/api/` | HTTP request functions organized by domain, such as `sys/` and `common/`. | +| `src/components/` | Reusable components, including generic components and JeecgBoot-specific components under `jeecg/`. | +| `src/hooks/` | Composition API hooks, including `web/`, `setting/`, and `system/`. | +| `src/layouts/` | App layouts, including `default/`, `iframe/`, and `page/`. | +| `src/router/` | Vue Router setup with dynamic route registration based on backend permissions. | +| `src/store/` | Pinia stores for app, user, permission, tabs, locale, and lock state. | +| `src/settings/` | Global project, component, and design settings. | +| `src/utils/` | HTTP client, auth/token management, encryption, dict helpers, and shared utilities. | +| `src/views/` | Feature pages, including `system/`, `dashboard/`, `monitor/`, and `super/`. | +| `src/locales/` | i18n files such as `zh_CN` and `en`. | +| `src/enums/` | TypeScript enums for constants. | +| `src/directives/` | Custom directives such as `v-auth`, `v-loading`, `v-click-outside`, and `v-ripple`. | + +## View Conventions + +Each feature module in `src/views/` typically includes: + +- `index.vue` for the main list page. +- `*.data.ts` for table column definitions and form schemas. +- `*.api.ts` for API endpoint calls. +- `*Modal.vue` or `*Drawer.vue` for detail and edit components. + +## Component Patterns + +Use `BasicTable` and `BasicForm` for list pages: + +```ts +import { BasicTable, useTable } from '/@/components/Table'; + +const [registerTable] = useTable({ api, columns, formConfig: { schemas } }); +``` + +Use `useModal` and `useDrawer` for detail panels: + +```ts +const [registerModal, { openModal }] = useModal(); +openModal(true, { isUpdate: true, record }); +``` + +Use `useListPage` from `src/hooks/system/` for standard CRUD pages that combine table, form, modal, and drawer behavior. + +Form schemas use `FormSchema[]` with `component` specifying Ant Design Vue or custom components such as `Input`, `Select`, `JDictSelectTag`, and `JSearchSelect`. + +## HTTP Client + +`defHttp` in `src/utils/http/axios/` wraps Axios with: + +- Token injection through interceptors. +- MD5 request signing for API security. +- Multi-tenant header support. +- Standard response unwrapping through the `result` field. + +API functions usually follow this pattern: + +```ts +enum Api { + List = '/sys/user/list', +} + +export const list = (params) => defHttp.get({ url: Api.List, params }); +``` + +## Authentication And Permissions + +- Tokens are stored in localStorage through auth utilities. +- `src/router/guard/permissionGuard.ts` fetches user info and dynamic menus from the backend. +- Permission mode is `BACK`, meaning routes and button permissions are backend-driven. +- Button-level auth uses the `v-auth="'system:user:add'"` directive or the `usePermission()` hook. + +## Component Settings + +Table pagination uses `pageNo` and `pageSize` params and expects `records` and `total` in the response. This is configured in `src/settings/componentSetting.ts`. + +## Environment Variables + +- `.env` contains base config such as port, app title, SSO, and qiankun toggles. +- `.env.development` contains dev proxy, mock toggle, and backend URL. +- `.env.production` contains production API URL and gzip config. +- Key variables include `VITE_GLOB_DOMAIN_URL`, `VITE_GLOB_API_URL`, and `VITE_PROXY`. + +## Build System + +`vite.config.ts` delegates plugin setup to `build/vite/plugin/`. Plugins include HTML template handling, mock data, gzip compression, SVG sprites, dynamic theme, qiankun micro-frontend, and PWA. Build scripts under `build/script/` handle post-build tasks. + +## Online Forms + +Online forms are a JeecgBoot low-code core feature. They generate CRUD pages from configuration. The full schema reference is in `src/views/super/online/cgform/online-form-schema.md` and covers table metadata, field configuration, control types, validation rules, and JavaScript enhancements. + +## Code Style + +- Prettier uses 150 character line width, single quotes, trailing commas for ES5, semicolons, and 2-space indentation. +- ESLint uses `vue/vue3-recommended`, `@typescript-eslint/recommended`, and Prettier with Prettier rules disabled. +- Unused variables prefixed with `_` are allowed. +- Prefer ` diff --git a/jeecgboot-vue3/src/components/Form/src/components/FormItem.vue b/jeecgboot-vue3/src/components/Form/src/components/FormItem.vue index af05918c3..902cfe3cf 100644 --- a/jeecgboot-vue3/src/components/Form/src/components/FormItem.vue +++ b/jeecgboot-vue3/src/components/Form/src/components/FormItem.vue @@ -164,24 +164,31 @@ return { isShow, isIfShow }; } // 代码逻辑说明: 【TV360X-434】validator校验执行两次 + //update-begin---author:liaozhiyang ---date:20260518 for:[issues/9638]新增prevValueArr,值发生变化时强制重新执行validator,避免上传/输入后必填红色提示不消失----------- let vSwitchArr: any = [], - prevValidatorArr: any = []; + prevValidatorArr: any = [], + prevValueArr: any = []; const hijackValidator = (rules) => { vSwitchArr = []; prevValidatorArr = []; + prevValueArr = []; rules.forEach((item, index) => { const fn = item.validator; vSwitchArr.push(true); prevValidatorArr.push(null); + prevValueArr.push(undefined); if (isFunction(fn)) { item.validator = (rule, value, callback) => { - if (vSwitchArr[index]) { + // 100ms内不允许多次调用,除非值变了 + const valueChanged = !Object.is(prevValueArr[index], value); + if (vSwitchArr[index] || valueChanged) { vSwitchArr[index] = false; setTimeout(() => { vSwitchArr[index] = true; }, 100); const result = fn(rule, value, callback); prevValidatorArr[index] = result; + prevValueArr[index] = value; return result; } else { return prevValidatorArr[index]; @@ -190,6 +197,7 @@ } }); }; + //update-end---author:liaozhiyang ---date:20260518 for:[issues/9638]新增prevValueArr,值发生变化时强制重新执行validator,避免上传/输入后必填红色提示不消失----------- function handleRules(): ValidationRule[] { const { rules: defRules = [], component, rulesMessageJoinLabel, label, dynamicRules, required, auth, field } = props.schema; // 代码逻辑说明: 【TV360X-857】解决禁用状态下触发校验 diff --git a/jeecgboot-vue3/src/components/Form/src/helper.ts b/jeecgboot-vue3/src/components/Form/src/helper.ts index 616b49ca6..775016e2d 100644 --- a/jeecgboot-vue3/src/components/Form/src/helper.ts +++ b/jeecgboot-vue3/src/components/Form/src/helper.ts @@ -26,6 +26,11 @@ export function createPlaceholderMessage(component: ComponentType) { // return `请选择${label}`; return t('common.chooseText'); } + //update-begin---author:liaozhiyang ---date:20260518 for:[issues/9638]BasicForm里使用JUpload且必填时验证总是提示未上传,补Upload类必填提示前缀----------- + if (component.includes('Upload')) { + return t('common.uploadText'); + } + //update-end---author:liaozhiyang ---date:20260518 for:[issues/9638]BasicForm里使用JUpload且必填时验证总是提示未上传,补Upload类必填提示前缀----------- return ''; } diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JAreaLinkage.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JAreaLinkage.vue index 734308e0b..718b67d7f 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JAreaLinkage.vue +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JAreaLinkage.vue @@ -1,5 +1,12 @@ + + diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JCascader.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JCascader.vue index f6f83d303..7ae2986d8 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JCascader.vue +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JCascader.vue @@ -4,8 +4,15 @@ --> @@ -69,11 +70,14 @@ detail: propTypes.bool.def(false), // 图片字段 imageField: propTypes.string.def(''), + // 查询模式:'online'(仅限 Online 表单配置的表)| 'table'(数据库中的所有表) + queryMode: propTypes.string.def('online'), }, components: { PlusOutlined, MinusCircleFilled, LinkTableListModal: createAsyncComponent(() => import('./components/LinkTableListModal.vue'), { loading: true }), + AllTableListModal: createAsyncComponent(() => import('./components/AllTableListModal.vue'), { loading: true }), }, emits: ['change', 'update:value'], setup(props, { emit }) { diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/AllTableListModal.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/AllTableListModal.vue new file mode 100644 index 000000000..9e5763d5f --- /dev/null +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/AllTableListModal.vue @@ -0,0 +1,528 @@ + + + + + diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue index f61a4e9f2..c208e94db 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/components/LinkTableListModal.vue @@ -12,306 +12,549 @@ 确定 - - + +
+ + + 已选中 {{ cardSelectedKeys.length }} 条记录 + 清空 + +
- - + +
+ +
+ +
+
+ + + + + - + +
+
{{ getCardMainContent(record) }}
+
+
+ {{ field.title }} + {{ record[field.key] }} +
+
+
- + +
+ +
+
+
- - - - - -
+ +
+ +
- + diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts index d0f579849..547c171f6 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useLinkTable.ts @@ -13,6 +13,39 @@ function queryTableColumns(tableName, params) { const url = '/online/cgform/api/getColumns/' + tableName; return defHttp.get({ url, params }); } +//表格列信息 +const tableColumns = ref([]); +// 选项 +const selectOptions = ref([]); +//字典项 +const dictOptions = ref({}); +/** + * table 模式:调用 /sys/dict/queryTableDataForLinkRecord 接口(一次请求获取多字段数据) + */ +function queryAllTableData(params: { + tableName: string; + showFields: string; + valueField: string; + keyword?: string; + searchFields?: string; + condition?: string; + keyValues?: string; + pageNo?: number; + pageSize?: number; +}) { + return defHttp.get({ url: '/sys/dict/queryTableDataForLinkRecord', params }); +} + +/** + * table 模式:获取需要查询的所有展示字段(textFields + imageField,去重) + */ +function getAllTableShowFields(props): string { + const textFields = (props.textField || '').split(',').filter(Boolean); + const imageField = props.imageField; + const fieldSet = new Set(textFields); + if (imageField) fieldSet.add(imageField); + return Array.from(fieldSet).join(','); +} export function useLinkTable(props) { //TODO 目前只支持查询第一页的数据,可以输入关键字搜索 @@ -50,6 +83,18 @@ export function useLinkTable(props) { watchEffect(async () => { const table = props.tableName; if (table) { + if (props.queryMode === 'table') { + // table 模式:跳过 Online 接口,根据 textField/imageField 配置初始化展示列 + const textFields = (props.textField || '').split(',').filter(Boolean); + mainContentField.value = textFields[0] || ''; + // otherColumns:textField 中第一个之后的字段,用于卡片副内容展示 + otherColumns.value = textFields.slice(1).map((f) => ({ title: f, dataIndex: f })); + // tableColumns 记录所有字段(transData 中使用,allTable 模式下实为 no-op) + tableColumns.value = textFields.map((f) => ({ title: f, dataIndex: f })); + dictOptions.value = {}; + return; + } + const valueField = props.valueField || ''; const textField = props.textField || ''; const arr: any[] = []; @@ -94,10 +139,6 @@ export function useLinkTable(props) { }; }); - // 选项 - const selectOptions = ref([]); - const tableColumns = ref([]); - const dictOptions = ref({}); async function resetTableColumns() { const params = baseParam.value; @@ -210,6 +251,34 @@ export function useLinkTable(props) { if (!value) { return []; } + + // table 模式:调用新接口,按 keyValues 反查所有字段,一次返回完整记录 + if (props.queryMode === 'table') { + const showFields = getAllTableShowFields(props); + if (!showFields) return []; + try { + const data = await queryAllTableData({ + tableName: props.tableName, + showFields, + valueField: props.valueField, + keyValues: value, + pageNo: 1, + pageSize: 200, + }); + const records: any[] = data?.records || []; + if (records.length > 0) return records; + // 接口无数据时用原始 value 兜底展示 + const firstTextField = (props.textField || '').split(',').filter(Boolean)[0] || props.valueField; + return value + .split(',') + .filter(Boolean) + .map((v: string) => ({ id: v, [props.valueField]: v, [firstTextField]: v })); + } catch (e) { + console.error('[useLinkTable] table loadOne 失败', e); + return []; + } + } + let valueFieldName = props.valueField; let params = { ...baseParam.value, diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useTableColumns.ts b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useTableColumns.ts new file mode 100644 index 000000000..439078213 --- /dev/null +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/hooks/useTableColumns.ts @@ -0,0 +1,648 @@ +import type { Ref } from 'vue'; +import type { ExtConfigType } from '../../types'; +import { HrefSlots, OnlineColumn } from '/@/components/jeecg/OnLine/types/onlineConfig'; +import { filterMultiDictObjs } from '/@/utils/dict/JDictSelectUtil'; +import { computed, defineAsyncComponent, h, reactive, ref, toRaw, unref, watch, markRaw } from 'vue'; +import { useRouter } from 'vue-router'; +import { Tag as ATag } from 'ant-design-vue'; +import { getFileAccessHttpUrl } from '/@/utils/common/compUtils'; +import { getAreaTextByCodeAnyLevel } from '/@/components/Form/src/utils/Area'; +import { createImgPreview } from '@/components/Preview'; +import { importViewsFile, _eval } from '/@/utils'; +import { useModal } from '/@/components/Modal'; +import LinkTableListPiece from '../../extend/linkTable/LinkTableListPiece.vue' +import { getToken } from "/@/utils/auth"; +import { downloadFile } from '/@/api/common/api'; +import { getWeekMonthQuarterYear, split } from '/@/utils'; +import { getItemColor } from "@/utils/dict/DictColors"; + +/** + * 获取实际列表需要的column配置 + * @param onlineTableContext 从数据库中查出来的数据 + * @param extConfigJson 扩展配置JSON + */ +export function useTableColumns(onlineTableContext, extConfigJson: Ref) { + // 获取路由器对象 href跳转用到 + let router = useRouter(); + + // 列信息 + const columns = ref>([]); + /** + * 20260309 + * liaozhiyang + * 【issues/9336】列宽拖动不了 + * */ + function applyResizableColumns(cols: OnlineColumn[]) { + cols.forEach((column) => { + if (!column.width) { + if (column.fieldType === 'date' || column.fieldType === 'Date') { + column.width = 120; + } else if (column.fieldType === 'link_table') { + column.width = 180; + } else { + column.width = 150; + } + } + column.resizable = true; + }); + } + + // 是否有bpm_status + //const hasBpmStatus = ref(false) + // 字典信息 + const dictOptionInfo = ref({}); + //已选择的值 + const selectedKeys = ref([]); + //选择的行记录 + //const selectRows = ref>([]); + // 选择列配置 --computed有问题 + const rowSelection = ref(null); + // 是否有滚动条 + let enableScrollBar = ref(true); + // table属性scroll + let tableScroll = computed(() => { + if (enableScrollBar.value == true) { + return undefined; + } else { + // X轴没有滚动条 + return { x: false }; + } + }); + + //用于 online列表的 某列的点击弹窗事件-弹窗显示其他表单 + const [registerOnlineHrefModal, { openModal: openOnlineHrefModal }] = useModal(); + const hrefMainTableId = ref('') + // 用于 online表单中 弹出别的表单 + const [registerPopModal, { openModal: openPopModal }] = useModal(); + const popTableId = ref('') + + // 对查询列信息的请求结果 处理方法 + function handleColumnResult(result, type = 'checkbox') { + // 字典设置 + dictOptionInfo.value = result.dictOptions; + // rowSelection设置 + if (result.checkboxFlag == 'Y') { + rowSelection.value = { + selectedRowKeys: selectedKeys, + onChange: onSelectChange, + type, + }; + } else { + rowSelection.value = null; + } + // 是否允许滚动条 + enableScrollBar.value = result.scrollFlag == 1; + + let dataColumns = result.columns; + dataColumns.forEach((column) => { + if (extConfigJson?.value?.canResizeColumn === 1) { + // update-begin--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + applyResizableColumns([column]); + // update-end--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + } + + // update-begin--author:liaozhiyang---date:20230818---for:【QQYUN-4161】列支持固定功能 + if (column.fieldExtendJson) { + const json = JSON.parse(column.fieldExtendJson); + if (!!json.isFixed) { + column.fixed = 'left'; + } + } + // update-end--author:liaozhiyang---date:20230818---for:【QQYUN-4161】列支持固定功能 + // update-begin--author:liaozhiyang---date:20240517---for:【TV360X-129】增加富文本控件配置href跳转 + if (column.hrefSlotName && column.scopedSlots) { + const obj = result.fieldHrefSlots?.find((item) => item.slotName === column.hrefSlotName); + if (obj) { + column.fieldHref = obj; + } + } + // update-end--author:liaozhiyang---date:20240517---for:【TV360X-129】增加富文本控件配置href跳转 + Object.keys(column).map((key) => { + // 删掉空值的字段(不删除 空字符串('') 或 数字 0 ) + if (column[key] == null) { + delete column[key]; + } + }); + }); + + // href 跳转 + let fieldHrefSlots: HrefSlots[] = result.fieldHrefSlots; + const fieldHrefSlotKeysMap = {}; + fieldHrefSlots.forEach((item) => (fieldHrefSlotKeysMap[item.slotName] = item)); + + let tableColumns: OnlineColumn[] = []; + // 处理列中的 href 跳转和 dict 字典,使两者可以兼容存在 + tableColumns = handleColumnHrefAndDict(dataColumns, fieldHrefSlotKeysMap); + // 是否有 bpm_status字段 如果有,列表操作按钮需要增加提交流程按钮 + bpmStatusFilter(tableColumns); + + console.log('-----列表列配置----', tableColumns); + // 如果是树列表 需要设置第一列字段 及 第一列align + if (onlineTableContext.isTree() === true) { + // 找到第一列的配置 + let firstField = result.textField; + let index = -1; + for (let i = 0; i < tableColumns.length; i++) { + if (tableColumns[i].dataIndex == firstField) { + index = i; + break; + } + } + if (index > 0) { + //如果是0或是-1不需要处理 + let deleteColumns = tableColumns.splice(index, 1); + tableColumns.unshift(deleteColumns[0]); + } + //第一列居左 + if (tableColumns.length > 0) { + tableColumns[0].align = 'left'; + } + } + columns.value = tableColumns; + // 列发生了变化,需要重新渲染表格 + onlineTableContext.reloadTable(); + } + + /** + * 表格选择事件 [expose] + * @param selectedRowKeys + * @param selectRow + */ + function onSelectChange(selectedRowKeys, selectedRows) { + selectedKeys.value = selectedRowKeys; + onlineTableContext['selectedRows'] = toRaw(selectedRows); + onlineTableContext['selectedRowKeys'] = toRaw(selectedRowKeys); + } + + /** + * 处理列的href和字典翻译 + */ + function handleColumnHrefAndDict(columns: OnlineColumn[], fieldHrefSlotKeysMap: {}): OnlineColumn[] { + for (let column of columns) { + let { customRender, hrefSlotName, fieldType } = column; + // online 报表中类型配置为日期(yyyy-MM-dd ),但是实际展示为日期时间格式(yyyy-MM-dd HH:mm:ss) issues/3042 + if (fieldType == 'date' || fieldType == 'Date') { + column.customRender = ({ text }) => { + if (!text) { + return ''; + } + if (text.length > 10) { + return text.substring(0, 10); + } + return text; + }; + } else if (fieldType == 'link_table') { + // 关联记录列表展示 + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + const fieldExtendJson = column.fieldExtendJson ?? '{}'; + const json = JSON.parse(fieldExtendJson); + // update-end--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + column.customRender = ({ text, record }) => { + if (!text) { + return ''; + } + if(onlineTableContext.isPopList===true){ + // 如果是弹窗的列表,关联记录的列只支持数据翻译,不需要跳转逻辑 + return record[column.dataIndex+"_dictText"] + }else{ + let tempIdArray = (text+'').split(','); + //update-begin-author:taoyan date:2023-2-15 for: QQYUN-4286【online表单】主子表开启联合查询 功能测试报错打不开 + let tempLabelArray = []; + if(record[column.dataIndex+"_dictText"]){ + tempLabelArray = record[column.dataIndex+"_dictText"].split(','); + } + //update-end-author:taoyan date:2023-2-15 for: QQYUN-4286【online表单】主子表开启联合查询 功能测试报错打不开 + let renderResult:any = [] + for(let i=0;ihandleClickLinkTable(id, hrefSlotName, json.isListReadOnly) + } + ); + renderResult.push(renderObj) + } + if(renderResult.length==0){ + return '' + } + //如果需要显示全,但是会换行:display: flex;width: 100%;flex-wrap: wrap;flex-direction: row; + return h('div',{style:{'overflow':'hidden'}}, renderResult); + } + }; + } else if (fieldType === 'popup_dict') { + // update-begin--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + column.customRender = ({ text, record }) => { + const dict = record[column.dataIndex + '_dictText']; + if (dict != undefined) { + return record[column.dataIndex + '_dictText']; + } + return text; + }; + // update-end--author:liaozhiyang---date:20240402---for:【QQYUN-8833】JPopupDict的列表翻译 + } else { + if (!hrefSlotName && column.scopedSlots && column.scopedSlots.customRender) { + //【Online报表】字典和href互斥 这里通过fieldHrefSlotKeysMap 先找到是href的列 + if (fieldHrefSlotKeysMap.hasOwnProperty(column.scopedSlots.customRender)) { + hrefSlotName = column.scopedSlots.customRender; + } + } + // 如果 customRender 有值则代表使用了字典 + // 如果 hrefSlotName 有值则代表使用了href跳转 + // 两者可以兼容。兼容的具体思路为:先获取到字典替换的值,再添加href链接跳转 + if (customRender || hrefSlotName) { + let dictCode = customRender as string; + let replaceFlag = '_replace_text_'; + // 自定义渲染函数的列 需要手动配置ellipsis + column.ellipsis = true; + column.customRender = ({ text, record }) => { + let value = text; + const valueSpan: any[] = []; + const getValue = () => valueSpan.length ? valueSpan : value; + // 如果 dictCode 有值,就进行字典转换 + if (dictCode) { + if (dictCode.startsWith(replaceFlag)) { + let textFieldName = dictCode.replace(replaceFlag, ''); + value = record[textFieldName]; + } else { + const dictItems = filterMultiDictObjs(unref(dictOptionInfo)[dictCode], text); + value = dictItems.map((item) => { + if (item.hasColor) { + //获取字体颜色 + const fontColor = getItemColor(item.color); + valueSpan.push(h(ATag, { + color: item.color, + style: { + 'color': fontColor, + 'margin-left': '5px', + }, + }, () => item.text)) + } + return item.text; + }).join(','); + } + } + // 扩展参数设置列的内容长度 + if (column.showLength) { + if (value && value.length > column.showLength) { + value = value.substr(0, column.showLength) + '...'; + } + } + // 如果 hrefSlotName 有值,就生成一个 a 标签,包裹住字典替换后(或原生)的值 + if (hrefSlotName) { + let field = fieldHrefSlotKeysMap[hrefSlotName]; + if (field) { + return h( + 'a', + { + onClick: () => handleClickFieldHref(field, record), + }, + getValue(), + ); + } + } + return h('span', {}, getValue()); + }; + } + + // 老版本叫scopedSlots 新版叫slots + if (column.scopedSlots) { + // slot的列 需要手动配置ellipsis + column.ellipsis = true; + let slots = column.scopedSlots; + column['slots'] = slots; + delete column.scopedSlots; + } + } + } + return columns; + } + + /** + * href 点击事件 + * @param field + * @param record + */ + function handleClickFieldHref(field, record) { + let href = field.href; + let urlPattern = /(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?/; + let compPattern = /\.vue(\?.*)?$/; + let jsPattern = /{{([^}]+)}}/g; // {{ xxx }} + if (typeof href === 'string') { + if(href.startsWith('ONLINE:')){ + // ONLINE:tableId:fieldName + let arr = href.split(':') + hrefMainTableId.value = arr[1]; + let fieldName = arr[2]; + openOnlineHrefModal(true, { + isUpdate: true, + disableSubmit: true, + hideSub: true, + record:{id: record[fieldName]}, + }) + }else{ + href = href.trim().replace(/\${([^}]+)?}/g, (_s1, s2) => record[s2]); + // 执行 {{...}} JS增强语句 + if (jsPattern.test(href)) { + href = href.replace(jsPattern, function (text, s0) { + try { + // 支持 {{ ACCESS_TOKEN }} 占位符 + if (s0.trim() === 'ACCESS_TOKEN') { + return getToken() + } + + // update-begin--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + return _eval(s0); + // update-end--author:liaozhiyang---date:20230904---for:【QQYUN-6390】eval替换成new Function,解决build警告 + } catch (e) { + console.error(e); + return text; + } + }); + } + if (urlPattern.test(href)) { + window.open(href, '_blank'); + } else if (compPattern.test(href)) { + // 处理弹框 + openHrefCompModal(href); + } else { + router.push(href); + } + } + } + } + + // 样式 + const dialogStyle = { + top: 0, + left: 0, + height: '100%', + margin: 0, + padding: 0, + }; + + // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + // 弹窗属性配置 + const hrefComponent = reactive({ + model: { + title: '', + okText: '关闭', + width: '100%', + open: false, + destroyOnClose: true, + style: dialogStyle, + // dialogStyle: dialogStyle, + bodyStyle: { padding: '8px', height: 'calc(100vh - 108px)', overflow: 'auto', overflowX: 'hidden' }, + // 隐藏掉取消按钮 + cancelButtonProps: { style: { display: 'none' } }, + }, + on: { + ok: () => (hrefComponent.model.open = false), + cancel: () => (hrefComponent.model.open = false), + }, + is: null, + params: {}, + }); + // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + + // 超链点击事件--> 打开一个modal窗口 + function openHrefCompModal(href) { + // 解析 href 参数 + let index = href.indexOf('?'); + let path = href; + if (index !== -1) { + path = href.substring(0, index); + let paramString = href.substring(index + 1, href.length); + let paramArray = paramString.split('&'); + let params = {}; + paramArray.forEach((paramObject) => { + let paramItem = paramObject.split('='); + params[paramItem[0]] = paramItem[1]; + }); + hrefComponent.params = params; + } else { + hrefComponent.params = {}; + } + // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + hrefComponent.model.open = true; + // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x + hrefComponent.model.title = '操作'; + hrefComponent.is = markRaw(defineAsyncComponent(() => importViewsFile(path))); + } + + //如果是树列表 操作列只能右侧固定 + let fixedAction:any = 'right'; + if(onlineTableContext.isTree()){ + fixedAction = 'right' + } + const actionColumn = reactive({ + title: '操作', + dataIndex: 'action', + slots: { customRender: 'action' }, + fixed: fixedAction, + align: 'center', + width: 150, + }); + + // 监听扩展参数的固定列配置,动态改变操作列的固定方式 +watch(() => extConfigJson?.value, () => { + if (extConfigJson?.value?.tableFixedAction === 1) { + actionColumn.fixed = extConfigJson?.value?.tableFixedActionType || 'right'; + if (onlineTableContext.isTree()) { + actionColumn.fixed = 'right'; + } + } + // update-begin--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 + if (extConfigJson?.value?.canResizeColumn === 1 && columns.value.length > 0) { + applyResizableColumns(columns.value); + onlineTableContext.reloadTable(); + } + // update-end--author:liaozhiyang---date:20260309---for:【issues/9336】列宽拖动不了 +}); + + // 流程按钮状态 + function bpmStatusFilter(tableColumns: OnlineColumn[]): boolean { + let flag = false; + for (let i = 0; i < tableColumns.length; i++) { + let item = tableColumns[i]; + let fieldName = item.dataIndex; + if (fieldName!.toLowerCase() == 'bpm_status') { + flag = true; + break; + } + } + onlineTableContext['hasBpmStatus'] = flag; + return flag; + } + + /** + * 文件 + * @param text + */ + function downloadRowFile(text, record, column, id) { + if (!text) { + return; + } + // update-begin--author:liaozhiyang---date:20240124---for:【QQYUN-8020】online 表单有多个文件走下载接口 + if (text.indexOf(',') > 0) { + downloadFile(`/online/cgform/field/download/${id}/${record.id}/${column.dataIndex}`, `文件_${record.id}.zip`); + } else { + const url = getFileAccessHttpUrl(text); + window.open(url); + } + // update-end--author:liaozhiyang---date:20240124---for:【QQYUN-8020】online 表单有多个文件走下载接口 + } + + /** + * 图片 + * @param text + */ + function getImgView(text) { + if (text && text.indexOf(',') > 0) { + // update-begin--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + text = split(text)[0]; + // update-end--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + } + return getFileAccessHttpUrl(text); + } + + /** + * 根据编码获取省市区文本 + * @param code + */ + function getPcaText(code, column) { + if (!code) { + return ''; + } + // update-begin--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + let includeParent = true; + let fieldExtendJson = column?.fieldExtendJson; + let level = 3; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (['province', 'city', 'region'].includes(fieldExtendJson.displayLevel)) { + if (fieldExtendJson.displayLevel === 'province') { + level = 1; + } else if (fieldExtendJson.displayLevel === 'city') { + level = 2; + } else if (fieldExtendJson.displayLevel === 'region') { + level = 3; + } + includeParent = false; + } + } + return getAreaTextByCodeAnyLevel(code, includeParent, level as 1 | 2 | 3); + // update-end--author:liaozhiyang---date:20260204---for:【QQYUN-14694】online支持配置独立的省、市、县 + } + + /** + * 日期格式化 + * @param text + */ + function getFormatDate(text, column) { + if (!text) { + return ''; + } + let a = text; + if (a.length > 10) { + a = a.substring(0, 10); + } + // update-begin--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + let fieldExtendJson = column?.fieldExtendJson; + if (fieldExtendJson) { + fieldExtendJson = JSON.parse(fieldExtendJson); + if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') { + const result = getWeekMonthQuarterYear(a); + return result[fieldExtendJson.picker]; + } + } + // update-end--author:liaozhiyang---date:20240430---for:【issues/6094】online 日期(年月日)控件增加年、年月,年周,年季度等格式 + return a; + } + + watch(selectedKeys, () => { + onlineTableContext['selectedRowKeys'] = toRaw(selectedKeys.value); + }); + + onlineTableContext['clearSelectedRow'] = () => { + selectedKeys.value = []; + onlineTableContext['selectedRows'] = []; + onlineTableContext['selectedRowKeys'] = []; + }; + + /** + * 预览列表 cell 图片 + * @param text + */ + function viewOnlineCellImage(text) { + if (text) { + let imgList: any = []; + // update-begin--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + const arr = split(text); + // update-end--author:liaozhiyang---date:20250325---for:【issues/7990】图片参数中包含逗号会错误的识别成多张图 + for (let str of arr) { + if (str) { + imgList.push(getFileAccessHttpUrl(str)); + } + } + createImgPreview({ imageList: imgList }); + } + } + + /** + * link table控件在列表上显示 支持点击跳转表单 + * @param id + * @param hrefTableName + */ + const onlinePopModalRef = ref(); + async function handleClickLinkTable(id, hrefTableName, isListReadOnly){ + popTableId.value = hrefTableName; + let formStatus = await onlinePopModalRef.value.getFormStatus(); + // 判断当前表单是否支持编辑,不能编辑跳详情表单 + if(formStatus==true){ + hrefMainTableId.value = hrefTableName; + openOnlineHrefModal(true, { + isUpdate: true, + disableSubmit: true, + hideSub: true, + record:{id: id}, + }) + }else{ + openPopModal(true, { + isUpdate: true, + // update-begin--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + disableSubmit: isListReadOnly ? true : false, + // update-end--author:liaozhiyang---date:20250318---for:【issues/7930】表格列表中支持关联记录配置是否只读 + record: { + id: id + } + }); + } + } + + return { + columns, + actionColumn, + selectedKeys, + rowSelection, + enableScrollBar, + tableScroll, + downloadRowFile, + getImgView, + getPcaText, + getFormatDate, + handleColumnResult, + onSelectChange, + hrefComponent, + viewOnlineCellImage, + hrefMainTableId, + registerOnlineHrefModal, + registerPopModal, + openPopModal, + openOnlineHrefModal, + onlinePopModalRef, + popTableId, + handleClickFieldHref, + }; +} diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JMultiTextTagInput.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JMultiTextTagInput.vue new file mode 100644 index 000000000..6eb45fa4b --- /dev/null +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JMultiTextTagInput.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopup.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopup.vue index 708caafd9..812c2c445 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopup.vue +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JPopup.vue @@ -2,9 +2,10 @@ diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue index 5139c5481..f97465fe5 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserListAndRole.vue @@ -126,11 +126,15 @@ } const maxHeight = ref(300); - maxHeight.value = window.innerHeight - 300; + //update-begin---wangshuai---date:20260810 for:【LHZP-1106】审批角色 按角色添加人员 高度太大了------------ + maxHeight.value = window.innerHeight - 500; + //update-end---wangshuai---date:20260810 for:【LHZP-1106】审批角色 按角色添加人员 高度太大了------------ const containerStyle = computed(() => { return { 'overflow-y': 'auto', - 'max-height': maxHeight.value + 'px', + //update-begin---wangshuai---date:20260810 for:【LHZP-1106】审批角色 按角色添加人员 高度太大了------------ + 'max-height': (maxHeight.value >= 300 ? maxHeight.value : 300) + 'px', + //update-end---author:wangshuai ---date:20260810 for:【LHZP-1106】审批角色 按角色添加人员 高度太大了------------ }; }); diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue index f523f7a2e..0d7b42aa2 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/userSelect/UserSelectModal.vue @@ -162,6 +162,7 @@ if (props.izExcludeMy) { excludeUserIdList.value.push(userStore.getUserInfo.id); } + resetFilterState(); //加载用户列表 loadUserList(); }); @@ -180,6 +181,7 @@ selectedDepart.value = ''; } function onDepartChange() { + pageNo.value = 1; loadUserList(); } /*--------------部门下拉框,用于筛选用户---------------*/ @@ -187,6 +189,17 @@ /*--------------第一页 搜索框---------------*/ const searchInputStatus = ref(false); const searchText = ref(''); + + /** + * 清空数据 + */ + function resetFilterState() { + selectedDepart.value = ''; + searchText.value = ''; + searchInputStatus.value = false; + pageNo.value = 1; + } + function showSearchInput(e) { e && prevent(e); searchInputStatus.value = true; diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useSelectBiz.ts b/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useSelectBiz.ts index a40ceccb6..475f1555b 100644 --- a/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useSelectBiz.ts +++ b/jeecgboot-vue3/src/components/Form/src/jeecg/hooks/useSelectBiz.ts @@ -1,4 +1,4 @@ -import { inject, reactive, ref, watch, unref, Ref } from 'vue'; +import { inject, reactive, ref, watch, unref, Ref, computed } from 'vue'; import { useMessage } from '/@/hooks/web/useMessage'; import { isEmpty } from '@/utils/is'; @@ -61,17 +61,16 @@ export function useSelectBiz(getList, props, emit?) { } /** - * 选择列配置 + * 选择列配置(响应式:当 isRadioSelection prop 变化时,type 自动切换 radio/checkbox) */ - const rowSelection = { - // 代码逻辑说明: 动态设置rowSelection的type值,默认是'checkbox' --- + const rowSelection = computed(() => ({ type: props.isRadioSelection ? 'radio' : 'checkbox', columnWidth: 20, selectedRowKeys: checkedKeys, onChange: onSelectChange, //table4.4.0新增属性选中之后是否清空上一页下一页的数据,默认false preserveSelectedRowKeys:true, - }; + })); /** * 序号列配置 diff --git a/jeecgboot-vue3/src/components/Form/src/types/form.ts b/jeecgboot-vue3/src/components/Form/src/types/form.ts index 75d530329..f7dac4d53 100644 --- a/jeecgboot-vue3/src/components/Form/src/types/form.ts +++ b/jeecgboot-vue3/src/components/Form/src/types/form.ts @@ -219,6 +219,8 @@ export interface HelpComponentProps { text: any; // colour color: string; + // Tooltip background colour + backgroundColor?: string; // font size fontSize: string; icon: string; diff --git a/jeecgboot-vue3/src/components/Form/src/types/index.ts b/jeecgboot-vue3/src/components/Form/src/types/index.ts index e2c97e094..9994f38a2 100644 --- a/jeecgboot-vue3/src/components/Form/src/types/index.ts +++ b/jeecgboot-vue3/src/components/Form/src/types/index.ts @@ -154,6 +154,7 @@ export type ComponentType = | 'LinkTableSelect' | 'LinkTableForQuery' | 'CascaderPcaForQuery' + | 'JCategoryLinkage' | 'CascaderPcaInFilter' | 'UserSelect' | 'RoleSelect' @@ -163,5 +164,5 @@ export type ComponentType = | 'RangeTime' | 'JRangeNumber' | 'JLinkTableCard' - | 'JInputSelect'; - + | 'JInputSelect' + | 'JAuthRuleValue'; diff --git a/jeecgboot-vue3/src/components/Icon/src/Icon.vue b/jeecgboot-vue3/src/components/Icon/src/Icon.vue index 075c57adc..266952997 100644 --- a/jeecgboot-vue3/src/components/Icon/src/Icon.vue +++ b/jeecgboot-vue3/src/components/Icon/src/Icon.vue @@ -9,6 +9,10 @@ import { isString } from '/@/utils/is'; import { propTypes } from '/@/utils/propTypes'; const SVG_END_WITH_FLAG = '|svg'; + // update-begin--author:liaozhiyang---date:20260713---for:【LHZP-143】online新增字段图标会闪烁 + // 缓存Iconify实例,避免每次动态import造成图标插入延迟(闪烁) + let iconifyInstance: any = null; + // update-end--author:liaozhiyang---date:20260713---for:【LHZP-143】online新增字段图标会闪烁 export default defineComponent({ name: 'Icon', components: { SvgIcon }, @@ -44,17 +48,21 @@ // update-begin--author:liaozhiyang---date:20260304---for:【QQYUN-14802】新增unplugin-icons插件,及icon支持online和local两种模式 try { let svg: SVGElement | null = null; - if (import.meta.env.VITE_GLOB_ICONIFY_USE_TYPE === 'local') { + // update-begin--author:liaozhiyang---date:20260713---for:【LHZP-143】online新增字段图标会闪烁 + if (iconifyInstance) { + svg = iconifyInstance.renderSVG(icon, {}); + } else if (import.meta.env.VITE_GLOB_ICONIFY_USE_TYPE === 'local') { // 使用本地 purge-icons 图标(离线,图标已打包进产物) const iconifyModule = await import('@purge-icons/generated'); - const Iconify = iconifyModule.default; - svg = Iconify.renderSVG(icon, {}); + iconifyInstance = iconifyModule.default; + svg = iconifyInstance.renderSVG(icon, {}); } else { // 使用 @iconify/iconify 在线按需加载 const iconifyModule = await import('@iconify/iconify'); - const Iconify = iconifyModule.default; - svg = Iconify.renderSVG(icon, {}); + iconifyInstance = iconifyModule.default; + svg = iconifyInstance.renderSVG(icon, {}); } + // update-end--author:liaozhiyang---date:20260713---for:【LHZP-143】online新增字段图标会闪烁 if (svg) { el.textContent = ''; el.appendChild(svg); diff --git a/jeecgboot-vue3/src/components/InFilter/DatePickerInFilter.vue b/jeecgboot-vue3/src/components/InFilter/DatePickerInFilter.vue index bc26ff7a9..8cdab8d8d 100644 --- a/jeecgboot-vue3/src/components/InFilter/DatePickerInFilter.vue +++ b/jeecgboot-vue3/src/components/InFilter/DatePickerInFilter.vue @@ -41,10 +41,16 @@ export default defineComponent({ - + diff --git a/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeImageCell.vue b/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeImageCell.vue index ee8d2cf9b..85f68c62d 100644 --- a/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeImageCell.vue +++ b/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeImageCell.vue @@ -21,7 +21,7 @@ - {{ originColumn.btnText || '上传图片' }} + {{ originColumn.btnText || '图片' }}
diff --git a/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue b/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue index b1228b0b0..968efb375 100644 --- a/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue +++ b/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxeLinkTableCell.vue @@ -8,98 +8,124 @@ :linkFields="originColumn.linkFields || []" :imageField="originColumn.imageField || ''" :editBtnShow="!originColumn.editBtnShow" + :queryMode="originColumn.queryMode || 'online'" v-bind="cellProps" @change="handleChange" /> diff --git a/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxePopupDictCell.vue b/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxePopupDictCell.vue new file mode 100644 index 000000000..ef284c8a3 --- /dev/null +++ b/jeecgboot-vue3/src/components/JVxeCustom/src/components/JVxePopupDictCell.vue @@ -0,0 +1,138 @@ + + + diff --git a/jeecgboot-vue3/src/components/JVxeCustom/src/hooks/useFileCell.ts b/jeecgboot-vue3/src/components/JVxeCustom/src/hooks/useFileCell.ts index 833f17553..9d818bdd3 100644 --- a/jeecgboot-vue3/src/components/JVxeCustom/src/hooks/useFileCell.ts +++ b/jeecgboot-vue3/src/components/JVxeCustom/src/hooks/useFileCell.ts @@ -7,6 +7,7 @@ import { JVxeComponent } from '/@/components/jeecg/JVxeTable/src/types/JVxeCompo import { Icon } from '/@/components/Icon'; import { Dropdown } from 'ant-design-vue'; import { LoadingOutlined } from '@ant-design/icons-vue'; +import { split } from '/@/utils'; export function useFileCell(props, fileType: UploadTypeEnum, options?) { const setup = useJVxeUploadCell(props, { token: true, action: uploadUrl, ...options }); @@ -15,14 +16,14 @@ export function useFileCell(props, fileType: UploadTypeEnum, options?) { const [registerModel, { openModal }] = useModal(); // 截取文件名 - const ellipsisFileName = computed(() => { - let length = 5; - let file = innerFile.value; + const shortFileName = computed(() => { + const length = 5; + const file = innerFile.value; if (!file || !file.name) { return ''; } if (file.name.length > length) { - return file.name.substr(0, length) + '…'; + return file.name.substr(0, length); } return file.name; }); @@ -38,14 +39,19 @@ export function useFileCell(props, fileType: UploadTypeEnum, options?) { return ''; }); + const uploadCount = computed(() => { + const value = modalValue.value; + return value ? split(value).filter(Boolean).length : 0; + }); + const maxCount = computed(() => { let maxCount = originColumn.value.maxCount; // online 扩展JSON if (originColumn.value && originColumn.value.fieldExtendJson) { - let json = JSON.parse(originColumn.value.fieldExtendJson); + const json = JSON.parse(originColumn.value.fieldExtendJson); maxCount = json.uploadnum ? json.uploadnum : 0; } - return maxCount ?? 0; + return Number(maxCount ?? 0); }); // 点击更多按钮 @@ -56,19 +62,26 @@ export function useFileCell(props, fileType: UploadTypeEnum, options?) { download: true, ...originColumn.value.props, maxCount: maxCount.value, + multiple: originColumn.value.props?.multiple ?? maxCount.value !== 1, fileType: fileType, - action: originColumn.value.action ?? void 0, + // update-begin--author:liaozhiyang---date:20250526---for:【issues/9657】jvxetable组件的图片上传编辑时再次上传报错 + action: originColumn.value.action ?? setup.uploadAction, + // update-end--author:liaozhiyang---date:20250526---for:【issues/9657】jvxetable组件的图片上传编辑时再次上传报错 + // update-begin--author:liaozhiyang---date:20250526---for:【issues/9652】vxetable上传、上传文件、上传图片加上自定义路径 + bizPath: originColumn.value.bizPath ?? 'temp', + // update-end--author:liaozhiyang---date:20250526---for:【issues/9652】vxetable上传、上传文件、上传图片加上自定义路径 }); } // 更多上传回调 function onModalChange(path) { if (path) { - // 代码逻辑说明: 【TV360X-235】富文本禁用状态下图片上传按钮文字看不清 if (innerFile.value === null) { - innerFile.value = {}; + // 从弹窗首次上传时同步补齐文件名和完成状态 + innerFile.value = fileSetValue(path); + } else { + innerFile.value.path = path; } - innerFile.value.path = path; handleChangeCommon(innerFile.value); } else { // 代码逻辑说明: [issues/530]JVxeTable 的JVxeTypes.image类型,无法全部删除上传图片 @@ -79,8 +92,9 @@ export function useFileCell(props, fileType: UploadTypeEnum, options?) { return { ...setup, modalValue, + uploadCount, maxCount, - ellipsisFileName, + shortFileName, registerModel, onModalChange, handleMoreOperation, diff --git a/jeecgboot-vue3/src/components/Markdown/src/MarkdownViewer.vue b/jeecgboot-vue3/src/components/Markdown/src/MarkdownViewer.vue index f25274a4d..7b33f282a 100644 --- a/jeecgboot-vue3/src/components/Markdown/src/MarkdownViewer.vue +++ b/jeecgboot-vue3/src/components/Markdown/src/MarkdownViewer.vue @@ -11,6 +11,7 @@ import 'vditor/dist/index.css'; import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { ThemeEnum } from '/@/enums/appEnum'; + import { sanitizeRichText } from '/@/utils/htmlSanitizer'; const converter = new showdown.Converter(); converter.setOption('tables', true); @@ -19,7 +20,7 @@ value: { type: String }, class: { type: String }, }); - const getHtmlData = computed(() => converter.makeHtml(props.value || '')); + const getHtmlData = computed(() => sanitizeRichText(converter.makeHtml(props.value || ''))); // 代码逻辑说明: 【issues/918】MarkdownViewer加上暗黑主题 const isDarkTheme = ref(false); diff --git a/jeecgboot-vue3/src/components/Modal/src/BasicModal.vue b/jeecgboot-vue3/src/components/Modal/src/BasicModal.vue index 4769ea57f..fababbb55 100644 --- a/jeecgboot-vue3/src/components/Modal/src/BasicModal.vue +++ b/jeecgboot-vue3/src/components/Modal/src/BasicModal.vue @@ -29,7 +29,7 @@ :maxHeight="getProps.maxHeight" :height="getWrapperHeight" :visible="visibleRef" - :modalHeaderHeight="getProps.modalHeaderHeight" + :modalHeaderHeight="isNoTitle ? 0 : getProps.modalHeaderHeight" :modalFooterHeight="footer !== undefined && !footer ? 0 : getProps.modalFooterHeight" v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')" @ext-height="handleExtHeight" @@ -266,6 +266,13 @@ emit('fullScreen',val); }); + // update-begin--author:liaozhiyang---date:20260805---for:【LHZP-865】basicModal无标题时不预留 header 高度,有标题时重新计算内容区高度 + watch(isNoTitle, () => { + nextTick(() => { + modalMethods.redoModalHeight(); + }); + }); + // update-end--author:liaozhiyang---date:20260805---for:【LHZP-865】basicModal无标题时不预留 header 高度,有标题时重新计算内容区高度 return { handleCancel, getBindValue, diff --git a/jeecgboot-vue3/src/components/Modal/src/components/ModalWrapper.vue b/jeecgboot-vue3/src/components/Modal/src/components/ModalWrapper.vue index b6ce0d820..bf368a7b9 100644 --- a/jeecgboot-vue3/src/components/Modal/src/components/ModalWrapper.vue +++ b/jeecgboot-vue3/src/components/Modal/src/components/ModalWrapper.vue @@ -38,6 +38,7 @@ const spinRef = ref(null); const realHeightRef = ref(0); const minRealHeightRef = ref(0); + const availableHeightRef = ref(0); let realHeight = 0; @@ -116,7 +117,7 @@ return { minHeight: `${props.minHeight === null ? defaultMiniHeight : props.minHeight}px`, // 代码逻辑说明: 【QQYUN-7641】basicModal组件添加MaxHeight属性 - maxHeight: `${props.maxHeight ? props.maxHeight : unref(realHeightRef)}px`, + maxHeight: `${props.maxHeight ? Math.min(props.maxHeight, unref(availableHeightRef) || props.maxHeight) : unref(realHeightRef)}px`, }; } } @@ -172,21 +173,24 @@ await nextTick(); try { - const modalDom = bodyDom.parentElement && bodyDom.parentElement.parentElement; + const modalDom = bodyDom.closest('.ant-modal'); if (!modalDom) return; + //update-begin---author:scott ---date:20260814 for:修复弹窗内容高度自适应及底部操作按钮遮挡问题----------- + const spinEl = unref(spinRef); + if (!spinEl) return; - const modalRect = getComputedStyle(modalDom as Element).top; - const modalTop = Number.parseInt(modalRect); - let maxHeight = window.innerHeight - modalTop * 2 + (props.footerOffset! || 0) - props.modalFooterHeight - props.modalHeaderHeight; + // 使用 CSS 布局位置计算可用高度,避免打开动画导致位置读取不稳定 + const modalTop = Number.parseFloat(getComputedStyle(modalDom).top) || 0; + const modalExtHeight = (modalDom as HTMLElement).offsetHeight - spinEl.clientHeight; + let maxHeight = window.innerHeight - modalTop + (props.footerOffset! || 0) - modalExtHeight; + //update-end---author:scott ---date:20260814 for:修复弹窗内容高度自适应及底部操作按钮遮挡问题----------- // 距离顶部过进会出现滚动条 if (modalTop < 40) { maxHeight -= 26; } + availableHeightRef.value = maxHeight; await nextTick(); - const spinEl = unref(spinRef); - - if (!spinEl) return; await nextTick(); // if (!realHeight) { realHeight = spinEl.scrollHeight; diff --git a/jeecgboot-vue3/src/components/Scrollbar/src/Scrollbar.vue b/jeecgboot-vue3/src/components/Scrollbar/src/Scrollbar.vue index 3ea4a026b..7687a537d 100644 --- a/jeecgboot-vue3/src/components/Scrollbar/src/Scrollbar.vue +++ b/jeecgboot-vue3/src/components/Scrollbar/src/Scrollbar.vue @@ -145,7 +145,7 @@ width: 0; height: 0; cursor: pointer; - background-color: rgba(144, 147, 153, 0.3); + background-color: rgba(144, 147, 153, 0.6); border-radius: inherit; transition: 0.3s background-color; diff --git a/jeecgboot-vue3/src/components/Tinymce/src/Editor.vue b/jeecgboot-vue3/src/components/Tinymce/src/Editor.vue index 538894d1a..e0c722978 100644 --- a/jeecgboot-vue3/src/components/Tinymce/src/Editor.vue +++ b/jeecgboot-vue3/src/components/Tinymce/src/Editor.vue @@ -18,7 +18,9 @@ diff --git a/jeecgboot-vue3/src/components/jeecg/JTabsSelectUser/useSelectUser.ts b/jeecgboot-vue3/src/components/jeecg/JTabsSelectUser/useSelectUser.ts index 05d6d758d..8d5b39e8c 100644 --- a/jeecgboot-vue3/src/components/jeecg/JTabsSelectUser/useSelectUser.ts +++ b/jeecgboot-vue3/src/components/jeecg/JTabsSelectUser/useSelectUser.ts @@ -20,7 +20,7 @@ export const getDepartTreeData = (params?) => defHttp.get({ url: Api.departList, /** * 获取用户列表 */ -export const getUserList = (params?) => defHttp.get({ url: Api.userList, params }); +export const getUserList = (params?) => defHttp.get({ url: Api.userList, params: { ...params, status: '1' } }); /** * 获取指定部门用户列表 diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue index 9c3241ba4..9bbf73144 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/JVxeToolbar.vue @@ -8,6 +8,7 @@ {{ addBtnCfg.buttonName }} + 保存 @@ -21,7 +22,7 @@ 清空选择 - + {{ collapsed ? '展开' : '收起' }} diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue index 61eedfe03..795a30837 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeDateCell.vue @@ -54,7 +54,10 @@ function handleChange(_mom, dateStr) { // 代码逻辑说明: 【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周 if (picker.value) { - handleChangeCommon(_mom); + //update-begin-author:liusq---date:2026-08-06--for: LHZP-1052 生成的jvxe子表 保存的时候 年 年月 周 季度 有一个没有填写值,就会报错 + const dateValue = _mom && dayjs(_mom).isValid() ? dayjs(_mom).format(dateFormat.value) : null; + handleChangeCommon(dateValue); + //update-end-author:liusq---date:2026-08-06--for: LHZP-1052 生成的jvxe子表 保存的时候 年 年月 周 季度 有一个没有填写值,就会报错 } else { handleChangeCommon(dateStr); } diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue index 251acd677..7f25866a2 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeSelectCell.vue @@ -4,10 +4,29 @@  加载中… - @@ -79,6 +98,24 @@ return originColumn.value.options; }); + // 是否包含分组字段 + const hasGroup = computed(() => { + const opts = selectOptions.value; + return opts && opts.length > 0 && opts.some((o) => o.group); + }); + + // 按 group 字段分组后的选项 + const groupedSelectOptions = computed(() => { + const opts = selectOptions.value || []; + const groups: Record = {}; + for (const opt of opts) { + const g = opt.group || ''; + if (!groups[g]) groups[g] = []; + groups[g].push(opt); + } + return groups; + }); + // --------- created --------- // 多选、搜索type @@ -169,6 +206,8 @@ innerValue, selectProps, selectOptions, + hasGroup, + groupedSelectOptions, handleChange, handleBlur, }; diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue index f9d73ec1e..9441db5d5 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeUploadCell.vue @@ -37,7 +37,7 @@ v-if="!cellProps.disabledTable" v-show="!hasFile" name="file" - :data="{ isup: 1 }" + :data="uploadData" :multiple="false" :action="originColumn.action" :headers="uploadHeaders" diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts index c3a7979fb..c6e930017 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/cells/useJVxeUploadCell.ts @@ -37,7 +37,16 @@ export function useJVxeUploadCell(props: JVxeComponent.Props, options?) { }); const hasFile = computed(() => innerFile.value != null); const responseName = computed(() => originColumn.value.responseName ?? 'message'); - + // update-begin--author:liaozhiyang---date:20250526---for:【issues/9652】vxetable上传、上传文件、上传图片加上自定义路径 + const uploadData = computed(() => { + const bizPath = originColumn.value.bizPath; + const result: any = { isup: 1 }; + if (bizPath) { + result.biz = bizPath; + } + return result; + }); + // update-end--author:liaozhiyang---date:20250526---for:【issues/9652】vxetable上传、上传文件、上传图片加上自定义路径 watch( innerValue, (val) => { @@ -110,6 +119,7 @@ export function useJVxeUploadCell(props: JVxeComponent.Props, options?) { innerFile, uploadAction, uploadHeaders, + uploadData, hasFile, responseName, handleChangeUpload, diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts index 073cb1af8..d8717035e 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts @@ -141,7 +141,9 @@ function customComponentAddStar(columns) { ].includes(type) ) { if (validateRules.find((item) => item.required)) { - column.title = ` * ${column.title}`; + // update-begin--author:liaozhiyang---date:20260807---for:【LHZP-703】用户、部门、图片、文件、开关的必填标识显示非红色 + column.headerClassName = 'jvxe-col--required'; + // update-end--author:liaozhiyang---date:20260807---for:【LHZP-703】用户、部门、图片、文件、开关的必填标识显示非红色 } } } diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts index e870b8370..d74161211 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useJVxeComponent.ts @@ -186,6 +186,28 @@ export function useJVxeComponent(props: JVxeComponent.Props) { { immediate: true } ); + // update-begin--author:liaozhiyang---date:20250526---for:【QQYUN-15511】一对多子表的options通过js修改后,刷新后编辑回显的不是label + // 监听 options 变化(解决 动态更新options时需重新翻译) + watch( + () => originColumn.value?.options, + () => { + if (props.renderType === JVxeRenderType.spaner && enhanced.translate.enabled === true) { + if (isFunction(enhanced.translate.handler)) { + const newValue = value.value; + if (newValue === undefined || newValue === null) return; + let res = enhanced.translate.handler(newValue, ctx); + if (isPromise(res)) { + res.then((v) => (innerValue.value = v)); + } else { + innerValue.value = res; + } + } + } + }, + { deep: true } + ); + // update-end--author:liaozhiyang---date:20250526---for:【QQYUN-15511】一对多子表的options通过js修改后,刷新后编辑回显的不是label + /** 通用处理 change 事件 */ function handleChangeCommon($value, force = false) { const newValue = enhanced.getValue($value, ctx); diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts index 8195db910..7b564637b 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useToolbar.ts @@ -61,6 +61,7 @@ export function useToolbar(props: JVxeTableProps, data: JVxeDataProps, methods: }, { toolbarPrefix: $slots.toolbarPrefix, + toolbarAfterAdd: $slots.toolbarAfterAdd, toolbarSuffix: $slots.toolbarSuffix, } ); diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts index 296cf9dbe..b25be2fdd 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts @@ -22,12 +22,18 @@ export function useValidateRules(args: HandleArgs) { delete rule.pattern; } else { // 兼容Online表单的特殊规则 + let reg: any = rule.pattern; for (let foo of fooPatterns) { if (foo.value === rule.pattern) { - rule.pattern = foo.pattern; + reg = foo.pattern; break; } } + // update-begin--author:liaozhiyang---date:20260714---for:【LHZP-172】行编辑pattern校验空值时不应触发,仅有值时校验(非空由required规则负责) + const regExp = reg instanceof RegExp ? reg : new RegExp(reg); + delete rule.pattern; + rule.validator = emptySkipPatternValidator(regExp); + // update-end--author:liaozhiyang---date:20260714---for:【LHZP-172】行编辑pattern校验空值时不应触发,仅有值时校验(非空由required规则负责) } } else if (typeof rule.handler === 'function') { // 自定义函数校验 @@ -78,6 +84,19 @@ function handlerConvertToValidator(event) { }); } +// update-begin--author:liaozhiyang---date:20260714---for:【LHZP-172】行编辑pattern校验空值时不应触发,仅有值时校验(非空由required规则负责) +/** pattern 校验器:空值不校验(非空交给 required 规则),仅当有值时做正则校验 */ +function emptySkipPatternValidator(regExp: RegExp) { + return function (event) { + const { cellValue, rule } = event; + if (cellValue === '' || cellValue === null || cellValue === undefined) { + return Promise.resolve(); + } + return regExp.test(String(cellValue)) ? Promise.resolve() : Promise.reject(new Error(rule.message)); + }; +} +// update-end--author:liaozhiyang---date:20260714---for:【LHZP-172】行编辑pattern校验空值时不应触发,仅有值时校验(非空由required规则负责) + // 兼容 online 的规则 const fooPatterns = [ { title: '非空', value: '*', pattern: /^.+$/ }, diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/install.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/install.ts index bbebb32ea..527b990de 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/install.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/install.ts @@ -70,7 +70,14 @@ function preventClosingPopUp(this: any, params) { return false; } // 点击的标签是JPopup的弹出层、部门选择、用户选择 - if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) { + if ( + className.includes('j-popup-modal') || + className.includes('j-depart-select-modal') || + className.includes('j-user-select-modal') || + className.includes('jeecg-online-pop-list-modal') || + className.includes('jeecg-online-pop-modal') || + className.includes('jeecg-all-table-pop-list-modal') + ) { return false; } // 点击的是日期选择器 diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/style/index.less b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/style/index.less index ff20d0db0..c34544f90 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/style/index.less +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/style/index.less @@ -7,6 +7,13 @@ border-color: #606266; } + // 图片、附件上传按钮在行内保持紧凑,避免抢占表格内容的视觉层级 + .j-vxe-upload-trigger.ant-btn { + height: 28px; + padding: 0 10px; + box-shadow: none; + } + .sort--active { border-color: @primary-color; } @@ -111,5 +118,17 @@ .vxe-header--column.col--ellipsis>.vxe-cell .vxe-cell--title{ font-size: 13px; } + // update-begin--author:liaozhiyang---date:20260807---for:【LHZP-703】用户、部门、图片、文件、开关的必填标识显示非红色 + // 不可编辑组件(cellRender)必填标识,样式与 vxe-table 原生 .vxe-cell--required-icon 保持一致 + .vxe-header--column.jvxe-col--required > .vxe-cell .vxe-cell--title::before { + content: '*'; + display: inline-block; + color: var(--vxe-ui-table-validate-error-color, #f5222d); + width: 0.8em; + margin-right: 2px; + font-family: Verdana, Arial, Tahoma; + vertical-align: middle; + } + // update-end--author:liaozhiyang---date:20260807---for:【LHZP-703】用户、部门、图片、文件、开关的必填标识显示非红色 } diff --git a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts index 6b7601af8..e65f66219 100644 --- a/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts +++ b/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/types/JVxeTypes.ts @@ -40,6 +40,9 @@ export enum JVxeTypes { // 以下为自定义组件 popup = 'popup', + // update-begin--author:liaozhiyang---date:20260507---for:【QQYUN-7961】vxetable子表支持popup字典 + popupDict = 'popupDict', + // update-end--author:liaozhiyang---date:20260507---for:【QQYUN-7961】vxetable子表支持popup字典 selectDictSearch = 'selectDictSearch', radio = 'radio', image = 'image', diff --git a/jeecgboot-vue3/src/components/jeecg/OnLine/SearchFormItem.vue b/jeecgboot-vue3/src/components/jeecg/OnLine/SearchFormItem.vue index cb766d0e1..33a207b63 100644 --- a/jeecgboot-vue3/src/components/jeecg/OnLine/SearchFormItem.vue +++ b/jeecgboot-vue3/src/components/jeecg/OnLine/SearchFormItem.vue @@ -9,6 +9,7 @@ valueFormat="YYYY-MM-DD" :placeholder="'请选择' + item.label" v-model:value="queryParam[item.field]" + style="width: 100%" > + + diff --git a/jeecgboot-vue3/src/views/demo/example/useBigScreenBridge.ts b/jeecgboot-vue3/src/views/demo/example/useBigScreenBridge.ts new file mode 100644 index 000000000..bb89dfcc3 --- /dev/null +++ b/jeecgboot-vue3/src/views/demo/example/useBigScreenBridge.ts @@ -0,0 +1,59 @@ +import { ref, onBeforeUnmount } from 'vue'; + +const PROTOCOL = 'bigscreen-bridge'; +const VERSION = 1; + +export function useBigScreenBridge(iframeRef, options) { + const channel = options.channel ?? 'default'; + const ready = ref(false); + const components = ref([]); + const designSize = ref(null); + const handlers = new Map(); + + function on(type, fn) { + if (!handlers.has(type)) handlers.set(type, new Set()); + handlers.get(type).add(fn); + return () => handlers.get(type)?.delete(fn); + } + + function onMessage(e) { + const msg = e.data; + if (!msg || msg.protocol !== PROTOCOL) return; + if (msg.channel !== channel) return; + if (msg.source !== 'bridge') return; + + if (msg.type === 'bridge:ready') { + const p = msg.payload || {}; + designSize.value = p.designSize || null; + components.value = Array.isArray(p.components) ? p.components : []; + ready.value = true; + } + + handlers.get(msg.type)?.forEach((fn) => fn(msg.payload, msg)); + handlers.get('*')?.forEach((fn) => fn(msg.payload, msg)); + } + + function send(type, payload) { + const win = iframeRef.value?.contentWindow; + if (!win) return; + win.postMessage( + { protocol: PROTOCOL, version: VERSION, channel, source: 'host', type, payload, ts: Date.now() }, + '*' + ); + } + + window.addEventListener('message', onMessage); + onBeforeUnmount(() => { + window.removeEventListener('message', onMessage); + handlers.clear(); + }); + + return { + ready, + components, + designSize, + on, + send, + sendComponentUpdate: (componentId, data) => send('host:component-update', { componentId, data }), + }; +} diff --git a/jeecgboot-vue3/src/views/demo/href-demo/index.vue b/jeecgboot-vue3/src/views/demo/href-demo/index.vue new file mode 100644 index 000000000..e018f23fa --- /dev/null +++ b/jeecgboot-vue3/src/views/demo/href-demo/index.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/jeecgboot-vue3/src/views/demo/jeecg/JVxeTableDemo/JVxeDemo1.vue b/jeecgboot-vue3/src/views/demo/jeecg/JVxeTableDemo/JVxeDemo1.vue index c0e73b355..049c24120 100644 --- a/jeecgboot-vue3/src/views/demo/jeecg/JVxeTableDemo/JVxeDemo1.vue +++ b/jeecgboot-vue3/src/views/demo/jeecg/JVxeTableDemo/JVxeDemo1.vue @@ -170,6 +170,18 @@ dictCode: 'sex', placeholder: '请选择', }, + // update-begin--author:liaozhiyang---date:20260507---for:【issues/7617】vxetable子表支持popup字典 + { + title: 'Popup字典(用户)', + key: 'popupUser', + type: JVxeTypes.popupDict, + width: 220, + // fieldConfig 格式:cgreport报表code,显示字段(label),存储字段(value) + fieldConfig: 'report_user,realname,username', + multi: true, + placeholder: '请选择', + }, + // update-end--author:liaozhiyang---date:20260507---for:【issues/7617】vxetable子表支持popup字典 { title: '下拉框_多选', key: 'select_multiple', @@ -298,6 +310,9 @@ return arr.join(','); })(), select_search: options[random(0, 3)], + // update-begin--author:liaozhiyang---date:20260507---for:【QQYUN-7961】vxetable子表支持popup字典 + popupUser: ['jeecg', 'admin'][random(0, 1)], + // update-end--author:liaozhiyang---date:20260507---for:【QQYUN-7961】vxetable子表支持popup字典 datetime: randomDatetime(), checkbox: ['Y', 'N'][random(0, 1)], planTimeRange: [dayjs().subtract(random(1, 30), 'day').format('YYYY-MM-DD'), dayjs().add(random(1, 30), 'day').format('YYYY-MM-DD')], diff --git a/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts b/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts index d5434d999..98806cbaf 100644 --- a/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts +++ b/jeecgboot-vue3/src/views/demo/jeecg/jeecgComponents.data.ts @@ -942,13 +942,54 @@ export const schemas: FormSchema[] = [ label: '用户选择', helpMessage: ['插槽模式-自己保存查询条件'], slot: 'tabsSelectUser', - colProps: { span: 14 }, + colProps: { span: 12 }, }, { field: 'tabsSelectUser', component: 'JEllipsis', label: '选中值', - colProps: { span: 10 }, + colProps: { span: 12 }, + }, + { + field: 'linkCard1', + component: 'JLinkTableCard', + label: '关联记录(online模式)', + helpMessage: ['component模式', 'queryMode=online(默认)', '仅支持 Online 表单中已配置的表'], + colProps: { span: 12 }, + componentProps: { + queryMode: 'online', + tableName: 'test_demo', + valueField: 'id', + textField: 'name,age,sex', + multi: false, + }, + }, + { + field: 'linkCard1', + component: 'JEllipsis', + label: '选中值(id)', + colProps: { span: 12 }, + }, + { + field: 'linkCard3', + component: 'JLinkTableCard', + label: '关联记录(table模式)', + helpMessage: ['component模式', 'queryMode=table + multi=true', '支持数据库中的所有表,多选'], + colProps: { span: 12 }, + componentProps: { + queryMode: 'table', + tableName: 'sys_user', + valueField: 'id', + textField: 'realname,username,sex', + imageField: 'avatar', + multi: true, + }, + }, + { + field: 'linkCard3', + component: 'JEllipsis', + label: '选中值(id逗号分隔)', + colProps: { span: 12 }, }, { field: 'orderAuth', diff --git a/jeecgboot-vue3/src/views/monitor/datalog/datalog.data.ts b/jeecgboot-vue3/src/views/monitor/datalog/datalog.data.ts index 85fe0cb53..437395cfb 100644 --- a/jeecgboot-vue3/src/views/monitor/datalog/datalog.data.ts +++ b/jeecgboot-vue3/src/views/monitor/datalog/datalog.data.ts @@ -82,7 +82,7 @@ export const searchFormSchema: FormSchema[] = [ { field: 'dataTable', label: '表名', - component: 'Input', + component: 'JInput', componentProps: { placeholder: '请输入表名', }, @@ -91,7 +91,7 @@ export const searchFormSchema: FormSchema[] = [ { field: 'dataId', label: '数据ID', - component: 'Input', + component: 'JInput', componentProps: { placeholder: '请输入数据ID', }, @@ -100,7 +100,7 @@ export const searchFormSchema: FormSchema[] = [ { field: 'createBy', label: '创建人', - component: 'Input', + component: 'JInput', componentProps: { placeholder: '请输入创建人', }, diff --git a/jeecgboot-vue3/src/views/monitor/datasource/DataSourceModal.vue b/jeecgboot-vue3/src/views/monitor/datasource/DataSourceModal.vue index e45d9d1c5..2317db8d5 100644 --- a/jeecgboot-vue3/src/views/monitor/datasource/DataSourceModal.vue +++ b/jeecgboot-vue3/src/views/monitor/datasource/DataSourceModal.vue @@ -4,12 +4,13 @@ @@ -41,9 +42,10 @@ if (unref(isUpdate)) { //获取详情 data.record = await getDataSourceById({ id: data.record.id }); - //表单赋值 + //【安全加固】编辑时不回填密码,留空表示不修改;测试和保存均由后端按id/code沿用原密码 await setFieldsValue({ ...data.record, + dbPassword: '', }); } }); @@ -51,12 +53,10 @@ const title = computed(() => (!unref(isUpdate) ? '新增数据源' : '编辑数据源')); async function handleTest() { - let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbName', 'dbUsername', 'dbPassword']; - // 获取以上字段的值,并清除校验状态 - let fieldsValues = getFieldsValue(keys); - let setFields = {}; - keys.forEach((key) => (setFields[key] = { value: fieldsValues[key], errors: null })); - await validateFields(keys).then((values) => { + let keys = ['dbType', 'dbDriver', 'dbUrl', 'dbUsername']; + await validateFields(keys).then(() => { + //【安全加固】取全部字段(含id、code),密码留空时由后端按id/code沿用原密码进行测试 + let values = getFieldsValue(); let loading = createMessage.loading('连接中....', 0); testConnection(values) .then((data) => { @@ -73,6 +73,7 @@ async function handleSubmit(v) { try { let values = await validate(); + //【安全加固】密码留空表示不修改,由后端沿用原密码保存 setModalProps({ confirmLoading: true }); //提交表单 await saveOrUpdateDataSource(values, isUpdate.value); diff --git a/jeecgboot-vue3/src/views/monitor/datasource/datasource.data.ts b/jeecgboot-vue3/src/views/monitor/datasource/datasource.data.ts index de403f8d2..13aa3799d 100644 --- a/jeecgboot-vue3/src/views/monitor/datasource/datasource.data.ts +++ b/jeecgboot-vue3/src/views/monitor/datasource/datasource.data.ts @@ -69,6 +69,12 @@ const dbUrlMap = { }; export const columns: BasicColumn[] = [ + { + title: '数据源编码', + dataIndex: 'code', + width: 180, + align: 'left', + }, { title: '数据源名称', dataIndex: 'name', @@ -100,7 +106,7 @@ export const searchFormSchema: FormSchema[] = [ { field: 'name', label: '数据源名称', - component: 'Input', + component: 'JInput', colProps: { span: 8 }, }, { @@ -173,7 +179,6 @@ export const formSchema: FormSchema[] = [ { field: 'dbPassword', label: '密码', - required: true, component: 'InputPassword', slot: 'pwd', }, diff --git a/jeecgboot-vue3/src/views/monitor/datasource/index.vue b/jeecgboot-vue3/src/views/monitor/datasource/index.vue index f12f5af4b..7b16515d7 100644 --- a/jeecgboot-vue3/src/views/monitor/datasource/index.vue +++ b/jeecgboot-vue3/src/views/monitor/datasource/index.vue @@ -56,7 +56,7 @@ name: '数据源列表', url: getExportUrl, }, - importConifg: { + importConfig: { url: getImportUrl, }, }); @@ -106,13 +106,17 @@ * 删除事件 */ async function handleDelete(record) { - await deleteDataSource({ id: record.id }, reload); + await deleteDataSource({ id: record.id }, handleSuccess); } /** * 批量删除事件 */ async function batchHandleDelete() { - await batchDeleteDataSource({ ids: selectedRowKeys.value }, reload); + await batchDeleteDataSource({ ids: selectedRowKeys.value }, handleSuccess); + } + + function handleSuccess() { + (selectedRowKeys.value = []) && reload(); } diff --git a/jeecgboot-vue3/src/views/monitor/mynews/DetailModal.vue b/jeecgboot-vue3/src/views/monitor/mynews/DetailModal.vue index fe9085374..deff74ae9 100644 --- a/jeecgboot-vue3/src/views/monitor/mynews/DetailModal.vue +++ b/jeecgboot-vue3/src/views/monitor/mynews/DetailModal.vue @@ -4,10 +4,9 @@ @register="registerModal" title="查看详情" :width="800" - :minHeight="600" + :height="500" :showCancelBtn="false" :showOkBtn="false" - :height="88" :destroyOnClose="true" @visible-change="handleVisibleChange" > @@ -18,26 +17,27 @@ 打印 - - - - - -
-
- 前往办理 + +
+
+
{{ content.titile }}
+
- +
+
+ 前往办理 +
+
+