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 @@
-->
-
+
@@ -36,7 +43,7 @@
+
+
diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JInput.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JInput.vue
index dbb1d3989..981079037 100644
--- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JInput.vue
+++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JInput.vue
@@ -85,7 +85,10 @@
}
switch (props.type) {
case JInputTypeEnum.JINPUT_QUERY_LIKE:
- text = '*' + text + '*';
+ // 如果输入以 ! 开头,表示走 NE(不等于)查询,不再拼 * 走 LIKE
+ if (!text.startsWith('!')) {
+ text = '*' + text + '*';
+ }
break;
case JInputTypeEnum.JINPUT_QUERY_NE:
text = '!' + text;
diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLeftEllipsis.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLeftEllipsis.vue
new file mode 100644
index 000000000..ea028e66c
--- /dev/null
+++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLeftEllipsis.vue
@@ -0,0 +1,138 @@
+
+ {{ displayText }}
+
+
+
+
+
diff --git a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue
index 7ea60c486..9cb076cb2 100644
--- a/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue
+++ b/jeecgboot-vue3/src/components/Form/src/jeecg/components/JLinkTableCard/JLinkTableCard.vue
@@ -35,7 +35,8 @@
-
+
+
@@ -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 @@
+
+
+
+ 关闭
+ 确定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ getMainContent(record) }}
+
+
+ {{ field }}
+ {{ record[field] }}
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
确定
-
-
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
- 无文件
- 下载
-
+
+
+
{{ getCardMainContent(record) }}
+
+
+ {{ field.title }}
+ {{ record[field.key] }}
+
+
+
-
- 无图片
-
-
+
+
+
![]()
+
+
+
-
-
-
-
-
- {{ getPcaText(text, column) }}
-
-
-
- {{ getFormatDate(text, column) }}
-
-
+
+
-
+
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 @@
+
+
+
+ {{ tag }}
+ ×
+
+
+
+
+ 使用"{{ inputValue }}"
+
+
+
+
+
+
+
+
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 @@
加载中…
-
-
- {{ option.text || option.label || option.title || option.value }}
-
+
+
+
+
+
+ {{ option.text || option.label || option.title || option.value }}
+
+
+
+
+
+
+
+
+ {{ option.text || option.label || option.title || option.value }}
+
+
@@ -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%"
>
@@ -40,6 +41,7 @@
:show-time="true"
valueFormat="YYYY-MM-DD HH:mm:ss"
v-model:value="queryParam[item.field]"
+ style="width: 100%"
>
@@ -66,7 +68,7 @@
{{ item.label }}
-
+
@@ -131,7 +134,12 @@
{{ item.label }}
-
+
@@ -324,4 +332,10 @@
text-overflow: ellipsis;
overflow-wrap: break-word;
}
+
+ /* update-begin--author:liaozhiyang---date:20260804---for:【LHZP-983】查询日期/日期时间组件宽度撑满 */
+ .jeecg-online-search :deep(.ant-picker) {
+ width: 100%;
+ }
+ /* update-end--author:liaozhiyang---date:20260804---for:【LHZP-983】查询日期/日期时间组件宽度撑满 */
diff --git a/jeecgboot-vue3/src/components/jeecg/OnLine/hooks/usePopBiz.ts b/jeecgboot-vue3/src/components/jeecg/OnLine/hooks/usePopBiz.ts
index a02592cf9..acf8ce654 100644
--- a/jeecgboot-vue3/src/components/jeecg/OnLine/hooks/usePopBiz.ts
+++ b/jeecgboot-vue3/src/components/jeecg/OnLine/hooks/usePopBiz.ts
@@ -255,9 +255,17 @@ export function usePopBiz(ob, tableRef?) {
if (query) {
url = url + query
}
+ // update-begin--author:liaozhiyang---date:20260804---for:【LHZP-1296】地址栏有参数时首次请求即带上,避免再触发两次请求
+ // 首次请求前先合并地址栏参数,保证与后续查询条件一致
+ if (props.routeQuery && Object.keys(props.routeQuery).length > 0) {
+ queryParam.value = Object.assign({}, queryParam.value, props.routeQuery);
+ }
+ let params = getQueryParams();
+ params['onlRepUrlParamStr'] = getUrlParamString();
+ // update-end--author:liaozhiyang---date:20260804---for:【LHZP-1296】地址栏有参数时首次请求即带上,避免再触发两次请求
//缓存key
- let groupIdKey = props.groupId ? `${props.groupId}${url}` : '';
- httpGroupRequest(() => defHttp.get({ url }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => {
+ let groupIdKey = props.groupId ? `${props.groupId}${url}${JSON.stringify(params)}` : '';
+ httpGroupRequest(() => defHttp.get({ url, params }, { isTransformResponse: false, successMessageMode: 'none' }), groupIdKey).then((res) => {
if (res.success) {
initDictOptionData(res.result.dictOptions);
cgRpConfigId.value = props.id;
@@ -621,15 +629,14 @@ export function usePopBiz(ob, tableRef?) {
dynamicParamHandler(res.result);
queryInfo.value = res.result;
console.log('queryInfo==>', queryInfo.value);
- //查询条件加载后再请求数据
+ // update-begin--author:liaozhiyang---date:20260804---for:【JPopupOnlReport】已有列表数据时不再二次 loadData,避免地址栏带参重复请求
+ // 查询条件加载后再请求数据;若 loadColumnsAndData 已带回数据,直接回填,不再重复请求
if (data) {
setDataSource(data);
- //传递路由参数和动态参数,不生效,
- loadData(1);
} else {
- //没有传递data时查询数据
loadData(1);
}
+ // update-end--author:liaozhiyang---date:20260804---for:【JPopupOnlReport】已有列表数据时不再二次 loadData,避免地址栏带参重复请求
} else {
createMessage.warning(res.message);
}
@@ -695,7 +702,7 @@ export function usePopBiz(ob, tableRef?) {
}
dataSource.value = data.records;
// 代码逻辑说明: issues/426 修复356时候引入的回归错误 JPopupOnlReportModal.vue 中未修改
- tableRef?.value && tableRef?.value?.setPagination({
+ tableRef?.value && tableRef?.value?.setPagination?.({
total: Number(data.total)
})
} else {
diff --git a/jeecgboot-vue3/src/components/jeecg/comment/CommentList.vue b/jeecgboot-vue3/src/components/jeecg/comment/CommentList.vue
index fa0c053f0..5623de345 100644
--- a/jeecgboot-vue3/src/components/jeecg/comment/CommentList.vue
+++ b/jeecgboot-vue3/src/components/jeecg/comment/CommentList.vue
@@ -17,7 +17,8 @@
{{ item.toUserId_dictText }}
@@ -85,7 +86,7 @@
// dayjs.extend(customParseFormat);
import { MessageOutlined } from '@ant-design/icons-vue';
- import { Comment, Tooltip } from 'ant-design-vue';
+ import { Comment, Spin, Tooltip } from 'ant-design-vue';
import { useUserStore } from '/@/store/modules/user';
import MyComment from './MyComment.vue';
import { list, saveOne, deleteOne, useCommentWithFile, useEmojiHtml, queryById, getGloablEmojiIndex } from './useComment';
@@ -115,21 +116,21 @@
setup(props) {
const { createMessage } = useMessage();
const dataList = ref([]);
- const { userInfo } = useUserStore();
+ const userStore = useUserStore();
const dayjs = inject('$dayjs')
const listRef = ref(null);
/**
* 获取当前用户名称
*/
function getMyname() {
- if (userInfo.realname) {
- return userInfo.realname.substr(0, 2);
+ if (userStore.getUserInfo.realname) {
+ return userStore.getUserInfo.realname.substr(0, 2);
}
return '';
}
function getMyAvatar(){
- return getFileAccessHttpUrl(userInfo.avatar);
+ return getFileAccessHttpUrl(userStore.getUserInfo.avatar);
}
// 获取头像
@@ -201,7 +202,7 @@
async function replyComment(item, content, fileList) {
console.log(content, item);
let obj = {
- fromUserId: userInfo.id,
+ fromUserId: userStore.getUserInfo.id,
toUserId: item.fromUserId,
commentId: item.id,
commentContent: content
@@ -213,7 +214,7 @@
//评论
async function sendComment(content, fileList) {
let obj = {
- fromUserId: userInfo.id,
+ fromUserId: userStore.getUserInfo.id,
commentContent: content
}
await saveCommentAndFiles(obj, fileList)
@@ -270,6 +271,7 @@
async function visibleChange(v, item){
if(v==true){
if(!item.commentId_dictText){
+ item.commentIdLoading = true;
const data = await queryById(item.commentId);
if(data.success == true){
item.commentId_dictText = data.result.commentContent
@@ -277,11 +279,13 @@
console.error(data.message)
item.commentId_dictText='该评论已被删除';
}
+ item.commentIdLoading = false;
}
}
}
// 代码逻辑说明: 【TV360X-932】评论加上换行
const lineFeed = (content) => {
+ if (!content) return '';
return content.replace(/\n/g, '
');
};
@@ -365,3 +369,14 @@
}
}
+
+
diff --git a/jeecgboot-vue3/src/components/jeecg/comment/DataLogList.vue b/jeecgboot-vue3/src/components/jeecg/comment/DataLogList.vue
index 5a443b4e5..35deb4de8 100644
--- a/jeecgboot-vue3/src/components/jeecg/comment/DataLogList.vue
+++ b/jeecgboot-vue3/src/components/jeecg/comment/DataLogList.vue
@@ -9,8 +9,8 @@
- @{{item.createName || item.createBy}}
- {{ item.dataContent }}
+ @{{item.createName || item.createBy}}
+ {{ item.dataContent }}
@@ -158,6 +158,9 @@
width: 100%;
word-break: break-word;
box-sizing: border-box;
+ span {
+ color: #333;
+ }
}
.log-item-date{
word-wrap: break-word;
diff --git a/jeecgboot-vue3/src/components/jeecg/comment/MyComment.vue b/jeecgboot-vue3/src/components/jeecg/comment/MyComment.vue
index 7e3d912dd..d869554fd 100644
--- a/jeecgboot-vue3/src/components/jeecg/comment/MyComment.vue
+++ b/jeecgboot-vue3/src/components/jeecg/comment/MyComment.vue
@@ -45,7 +45,7 @@
+
+
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 @@
+
+
+
+
+ Online 表单 Href 跳转
+
+
+ 路由参数 (route.query)
+
+
+ {{ value }}
+ (空值)
+
+
+
+
+
+ 完整信息
+
+ {{ route.fullPath }}
+ {{ route.name || '(无)' }}
+ {{ Object.keys(props).filter(k => props[k]).length > 0 ? '内部组件 (props)' : '路由跳转 (query)' }}
+ {{ Object.keys(queryParams).length }}
+
+
+
+
+ 返回上一页
+ 复制参数信息
+
+
+
+
+
+
+
+
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.sender }}
-
发布时间:{{ content.sendTime }}
-
-
- {{ content.visitsNum }}
-
-
-
-
-
-
-
-
-
前往办理
+
+
+
+
{{ content.titile }}
+
+
{{ priorityLabel }}
+
{{ content.sender }}
+
{{ formatSendDate(content.sendTime) }}
+
+
+ {{ content.visitsNum }}
+
+
+
-
+
+
+
+
相关附件:
@@ -74,14 +74,16 @@
import { useRouter } from 'vue-router';
import xss from 'xss';
import { options } from './XssWhiteList';
- import { ref, unref } from 'vue';
+ import { ref, unref, computed } from 'vue';
+ import dayjs from 'dayjs';
+ import { getDictItemsByCode } from '/@/utils/dict';
import { getElectronFileUrl, getFileAccessHttpUrl } from '@/utils/common/compUtils';
import { useGlobSetting } from '@/hooks/setting';
import { encryptByBase64 } from '@/utils/cipher';
import { getToken } from '@/utils/auth';
import {defHttp} from "@/utils/http/axios";
import {$electron} from "@/electron";
- import { removeSpecialTags } from '@/utils/index';
+ import { decodeHtmlEntities, removeSpecialTags } from '@/utils/index';
const router = useRouter();
const glob = useGlobSetting();
const isUpdate = ref(true);
@@ -101,7 +103,10 @@
// 代码逻辑说明: VUEN-1702 【禁止问题】sql注入漏洞
if (data.record.msgContent) {
// 代码逻辑说明: 【QQYUN-7049】3.6.0版本 通知公告中发布的富文本消息,在我的消息中查看没有样式---
- data.record.msgContent = xss(data.record.msgContent, options);
+ // update-begin--author:liaozhiyang---date:20260807---for:【LHZP-1128】修复thead标签没解析出来
+ // 先解码 HTML 实体(修复 thead 等标签被转义后与 table 等未转义标签混存的问题),再 XSS 过滤
+ data.record.msgContent = xss(decodeHtmlEntities(data.record.msgContent), options);
+ // update-end--author:liaozhiyang---date:20260807---for:【LHZP-1128】修复thead标签没解析出来
}
// 代码逻辑说明: [QQYUN-12521]通知公告消息增加访问量
@@ -129,6 +134,25 @@
});
const hasHref = ref(false);
+
+ // update-begin--author:liaozhiyang---date:20260807---for:【LHZP-1253】我的消息查看弹窗样式对齐通知公告查看弹窗
+ const priorityLabel = computed(() => {
+ if (!content.value?.priority) {
+ return '';
+ }
+ const dictItems = getDictItemsByCode('priority') || [];
+ const matched = dictItems.find((item) => item.value == content.value.priority);
+ return matched?.text || '';
+ });
+
+ function formatSendDate(date?: string) {
+ if (!date) {
+ return '';
+ }
+ return dayjs(date).format('YYYY年MM月DD日 HH:mm:ss');
+ }
+ // update-end--author:liaozhiyang---date:20260807---for:【LHZP-1253】我的消息查看弹窗样式对齐通知公告查看弹窗
+
//查看消息详情可以跳转
function showHrefButton() {
if (content.value.busId) {
@@ -195,7 +219,7 @@
style.innerHTML = `
body {
margin: 0;
- padding: 15px;
+ padding: 0;
font-family: Arial, sans-serif;
}
img {
@@ -206,28 +230,92 @@
size: auto;
margin: 15mm;
}
- @media print {
- body {
- padding: 0;
- }
+ .daily-article {
+ padding: 20px 16px 12px;
}
- .ant-card-meta-detail {
- display: flex !important ;
- justify-content: center !important;
- align-items: center !important;
- flex-direction: column !important;
+ .article-title {
+ font-size: 22px;
+ line-height: 1.4;
+ margin: 0 0 14px;
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.88);
}
- .ant-card-meta-title {
- font-size: 22px !important;
- color: rgba(51, 51, 51, 0.88);
- font-weight: 600;
- font-size: 16px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
+ .article-meta {
+ margin-bottom: 22px;
+ line-height: 20px;
+ font-size: 0;
}
- .ant-card .ant-card-meta-description {
- color: rgba(51, 51, 51, 0.45);
+ .article-meta-item {
+ display: inline-block;
+ vertical-align: middle;
+ margin: 0 10px 10px 0;
+ font-size: 15px;
+ }
+ .meta-name,
+ .meta-date,
+ .meta-visits {
+ color: rgba(0, 0, 0, 0.3);
+ }
+ .meta-visits {
+ display: inline-flex;
+ align-items: center;
+ }
+ .meta-visits .item-icon {
+ margin-right: 4px;
+ }
+ .priority-tag {
+ padding: 0 4px;
+ font-size: 12px;
+ line-height: 1.67;
+ border: 1px solid #d9d9d9;
+ border-radius: 4px;
+ background: rgba(0, 0, 0, 0.05);
+ color: rgba(0, 0, 0, 0.3);
+ margin-right: 8px;
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+ .priority-tag--H {
+ color: #f5222d;
+ background: #fff1f0;
+ border-color: #ffcfbf;
+ }
+ .priority-tag--M {
+ color: #fa8c16;
+ background: #fff7e6;
+ border-color: #ffe59a;
+ }
+ .priority-tag--L {
+ color: rgba(0, 0, 0, 0.3);
+ background: rgba(0, 0, 0, 0.05);
+ border-color: #d9d9d9;
+ }
+ .article-content {
+ color: rgba(0, 0, 0, 0.88);
+ font-size: 14px;
+ line-height: 1.8;
+ }
+ .article-content table {
+ width: 100%;
+ border-collapse: collapse !important;
+ border-spacing: 0 !important;
+ }
+ .article-content table td,
+ .article-content table th {
+ border: 1px solid #d0d0d0;
+ padding: 8px 12px;
+ min-width: 20px;
+ word-break: break-word;
+ }
+ .article-content table thead th {
+ background-color: #fafafa;
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.88);
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ }
+ .article-action {
+ margin-top: 16px;
}
`;
frameDoc.head.appendChild(style);
@@ -265,6 +353,7 @@
document.body.appendChild(printFrame);
}
+ // update-end--author:liaozhiyang---date:20260807---for:【LHZP-1253】打印样式对齐通知公告 showContent.ftl
/**
* 下载文件
@@ -301,18 +390,81 @@
diff --git a/jeecgboot-vue3/src/views/openapi/components/OpenApiModal.vue b/jeecgboot-vue3/src/views/openapi/components/OpenApiModal.vue
index 0e88e75ed..aad9cbb76 100644
--- a/jeecgboot-vue3/src/views/openapi/components/OpenApiModal.vue
+++ b/jeecgboot-vue3/src/views/openapi/components/OpenApiModal.vue
@@ -2,7 +2,20 @@
-
+
+
+
+
+
+
+
+
@@ -47,7 +60,7 @@
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { useJvxeMethod } from '/@/hooks/system/useJvxeMethods.ts';
- import { formSchema, openApiHeaderJVxeColumns, openApiParamJVxeColumns } from '../OpenApi.data';
+ import { formSchema, openApiHeaderJVxeColumns, openApiParamJVxeColumns, requestMethodOptions } from '../OpenApi.data';
import { saveOrUpdate, queryOpenApiHeader, queryOpenApiParam, getGenPath } from '../OpenApi.api';
import { VALIDATE_FAILED } from '/@/utils/common/vxeUtils';
import { useMessage } from "@/hooks/web/useMessage";
@@ -139,9 +152,9 @@
let headersJson = !!values.headersJson?JSON.stringify(values.headersJson):null;
let paramsJson = !!values.headersJson?JSON.stringify(values.paramsJson):null;
try {
- if (!!values.body){
+ if (!!values.requestBody){
try {
- if (typeof JSON.parse(values.body)!='object'){
+ if (typeof JSON.parse(values.requestBody)!='object'){
$message.createMessage.error("JSON格式化错误,请检查输入数据");
return;
}
@@ -153,6 +166,7 @@
setModalProps({ confirmLoading: true });
values.headersJson = headersJson
values.paramsJson = paramsJson
+ console.log("values*****model》》》》",values)
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
diff --git a/jeecgboot-vue3/src/views/super/airag/aiapp/AiApp.api.ts b/jeecgboot-vue3/src/views/super/airag/aiapp/AiApp.api.ts
index 4cd6b807e..b8bb7a1c7 100644
--- a/jeecgboot-vue3/src/views/super/airag/aiapp/AiApp.api.ts
+++ b/jeecgboot-vue3/src/views/super/airag/aiapp/AiApp.api.ts
@@ -5,6 +5,7 @@ export enum Api {
//知识库管理
list = '/airag/app/list',
save = '/airag/app/edit',
+ copy = '/airag/app/copy',
release = '/airag/app/release',
delete = '/airag/app/delete',
queryById = '/airag/app/queryById',
@@ -56,6 +57,16 @@ export const saveApp = (params) => {
return defHttp.put({ url: Api.save, params });
};
+/**
+ * 复制应用
+ * @param params
+ * @author scott
+ * @since 2026-08-06 【LHZP-1512】AI应用增加后台复制接口
+ */
+export const copyApp = (params) => {
+ return defHttp.post({ url: Api.copy, params }, { joinParamsToUrl: true });
+};
+
// 发布应用
export function releaseApp(appId: string, release = false) {
return defHttp.post({
@@ -64,7 +75,7 @@ export function releaseApp(appId: string, release = false) {
id: appId,
release: release,
}
- }, {joinParamsToUrl: true});
+ }, {joinParamsToUrl: true, isTransformResponse: false});
}
/**
diff --git a/jeecgboot-vue3/src/views/super/airag/aiapp/AiAppList.vue b/jeecgboot-vue3/src/views/super/airag/aiapp/AiAppList.vue
index a20594bd9..1e62ecd22 100644
--- a/jeecgboot-vue3/src/views/super/airag/aiapp/AiAppList.vue
+++ b/jeecgboot-vue3/src/views/super/airag/aiapp/AiAppList.vue
@@ -44,7 +44,7 @@
-
+
-
-
{{ item.descr || '暂无描述' }}