From 51ee3fb460b2e54adb9cd1b772529b7be37b9b26 Mon Sep 17 00:00:00 2001 From: wcg Date: Sun, 4 Jan 2026 13:45:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(project):=20=E6=B7=BB=E5=8A=A0vben5?= =?UTF-8?q?=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Yi.Vben5.Vue3/.browserslistrc | 4 + Yi.Vben5.Vue3/.changeset/README.md | 5 + Yi.Vben5.Vue3/.changeset/config.json | 18 + Yi.Vben5.Vue3/.commitlintrc.js | 1 + Yi.Vben5.Vue3/.dockerignore | 7 + Yi.Vben5.Vue3/.editorconfig | 18 + Yi.Vben5.Vue3/.gitattributes | 11 + Yi.Vben5.Vue3/.gitconfig | 2 + Yi.Vben5.Vue3/.gitignore | 54 + Yi.Vben5.Vue3/.gitpod.yml | 6 + Yi.Vben5.Vue3/.node-version | 1 + Yi.Vben5.Vue3/.npmrc | 13 + Yi.Vben5.Vue3/.prettierignore | 18 + Yi.Vben5.Vue3/.prettierrc.mjs | 1 + Yi.Vben5.Vue3/.stylelintignore | 4 + Yi.Vben5.Vue3/CHANGELOG.md | 323 ++++ Yi.Vben5.Vue3/LICENSE | 21 + Yi.Vben5.Vue3/README.md | 128 ++ Yi.Vben5.Vue3/apps/backend-mock/.env | 3 + Yi.Vben5.Vue3/apps/backend-mock/README.md | 15 + .../apps/backend-mock/api/auth/codes.ts | 14 + .../apps/backend-mock/api/auth/login.post.ts | 36 + .../apps/backend-mock/api/auth/logout.post.ts | 15 + .../backend-mock/api/auth/refresh.post.ts | 33 + .../apps/backend-mock/api/demo/bigint.ts | 28 + .../apps/backend-mock/api/menu/all.ts | 13 + Yi.Vben5.Vue3/apps/backend-mock/api/status.ts | 5 + .../backend-mock/api/system/dept/.post.ts | 15 + .../api/system/dept/[id].delete.ts | 15 + .../backend-mock/api/system/dept/[id].put.ts | 15 + .../apps/backend-mock/api/system/dept/list.ts | 61 + .../apps/backend-mock/api/system/menu/list.ts | 12 + .../api/system/menu/name-exists.ts | 28 + .../api/system/menu/path-exists.ts | 28 + .../apps/backend-mock/api/system/role/list.ts | 83 ++ .../apps/backend-mock/api/table/list.ts | 73 + .../apps/backend-mock/api/test.get.ts | 1 + .../apps/backend-mock/api/test.post.ts | 1 + Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts | 13 + .../apps/backend-mock/api/user/info.ts | 10 + Yi.Vben5.Vue3/apps/backend-mock/error.ts | 7 + .../apps/backend-mock/middleware/1.api.ts | 19 + .../apps/backend-mock/nitro.config.ts | 20 + Yi.Vben5.Vue3/apps/backend-mock/package.json | 21 + .../apps/backend-mock/routes/[...].ts | 13 + .../apps/backend-mock/tsconfig.build.json | 4 + Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json | 3 + .../apps/backend-mock/utils/cookie-utils.ts | 26 + .../apps/backend-mock/utils/jwt-utils.ts | 59 + .../apps/backend-mock/utils/mock-data.ts | 390 +++++ .../apps/backend-mock/utils/response.ts | 68 + Yi.Vben5.Vue3/apps/web-antd/.env | 8 + Yi.Vben5.Vue3/apps/web-antd/.env.analyze | 7 + Yi.Vben5.Vue3/apps/web-antd/.env.development | 25 + Yi.Vben5.Vue3/apps/web-antd/.env.production | 35 + Yi.Vben5.Vue3/apps/web-antd/.env.test | 35 + Yi.Vben5.Vue3/apps/web-antd/index.html | 22 + Yi.Vben5.Vue3/apps/web-antd/package.json | 64 + .../apps/web-antd/postcss.config.mjs | 1 + .../web-antd/src/adapter/component/index.ts | 258 ++++ .../apps/web-antd/src/adapter/form.ts | 56 + .../apps/web-antd/src/adapter/vxe-table.ts | 137 ++ .../apps/web-antd/src/api/common.d.ts | 42 + .../apps/web-antd/src/api/core/auth.ts | 204 +++ .../apps/web-antd/src/api/core/captcha.ts | 42 + .../apps/web-antd/src/api/core/index.ts | 4 + .../apps/web-antd/src/api/core/menu.ts | 45 + .../apps/web-antd/src/api/core/upload.ts | 47 + .../apps/web-antd/src/api/core/user.ts | 47 + Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts | 28 + Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts | 1 + .../web-antd/src/api/monitor/cache/index.ts | 90 ++ .../src/api/monitor/logininfo/index.ts | 61 + .../src/api/monitor/logininfo/model.d.ts | 11 + .../web-antd/src/api/monitor/online/index.ts | 46 + .../src/api/monitor/online/model.d.ts | 10 + .../web-antd/src/api/monitor/operlog/index.ts | 48 + .../src/api/monitor/operlog/model.d.ts | 14 + .../apps/web-antd/src/api/request.ts | 322 ++++ .../apps/web-antd/src/api/service/index.ts | 12 + .../apps/web-antd/src/api/service/model.d.ts | 46 + .../web-antd/src/api/system/client/index.ts | 77 + .../web-antd/src/api/system/client/model.d.ts | 12 + .../web-antd/src/api/system/config/index.ts | 78 + .../web-antd/src/api/system/config/model.d.ts | 14 + .../web-antd/src/api/system/dept/index.ts | 64 + .../web-antd/src/api/system/dept/model.d.ts | 14 + .../src/api/system/dict/dict-data-model.d.ts | 17 + .../web-antd/src/api/system/dict/dict-data.ts | 78 + .../src/api/system/dict/dict-type-model.d.ts | 13 + .../web-antd/src/api/system/dict/dict-type.ts | 87 ++ .../web-antd/src/api/system/menu/index.ts | 84 ++ .../web-antd/src/api/system/menu/model.d.ts | 57 + .../web-antd/src/api/system/notice/index.ts | 53 + .../web-antd/src/api/system/notice/model.d.ts | 13 + .../src/api/system/oss-config/index.ts | 48 + .../src/api/system/oss-config/model.d.ts | 16 + .../apps/web-antd/src/api/system/oss/index.ts | 77 + .../web-antd/src/api/system/oss/model.d.ts | 28 + .../web-antd/src/api/system/post/index.ts | 77 + .../web-antd/src/api/system/post/model.d.ts | 17 + .../web-antd/src/api/system/profile/index.ts | 65 + .../src/api/system/profile/model.d.ts | 75 + .../web-antd/src/api/system/role/index.ts | 161 ++ .../web-antd/src/api/system/role/model.d.ts | 30 + .../web-antd/src/api/system/social/index.ts | 25 + .../web-antd/src/api/system/social/model.d.ts | 26 + .../src/api/system/tenant-package/index.ts | 93 ++ .../src/api/system/tenant-package/model.d.ts | 17 + .../web-antd/src/api/system/tenant/index.ts | 113 ++ .../web-antd/src/api/system/tenant/model.d.ts | 26 + .../web-antd/src/api/system/user/index.ts | 156 ++ .../web-antd/src/api/system/user/model.d.ts | 100 ++ .../apps/web-antd/src/api/tool/gen/index.ts | 103 ++ .../apps/web-antd/src/api/tool/gen/model.d.ts | 187 +++ .../src/api/workflow/category/index.ts | 63 + .../src/api/workflow/category/model.d.ts | 97 ++ .../src/api/workflow/definition/index.ts | 155 ++ .../src/api/workflow/definition/model.d.ts | 19 + .../src/api/workflow/instance/index.ts | 120 ++ .../src/api/workflow/instance/model.d.ts | 41 + .../web-antd/src/api/workflow/task/index.ts | 172 +++ .../web-antd/src/api/workflow/task/model.d.ts | 108 ++ Yi.Vben5.Vue3/apps/web-antd/src/app.vue | 43 + Yi.Vben5.Vue3/apps/web-antd/src/bootstrap.ts | 79 + .../web-antd/src/components/cropper/index.ts | 3 + .../components/cropper/src/cropper-avatar.vue | 170 +++ .../components/cropper/src/cropper-modal.vue | 382 +++++ .../src/components/cropper/src/cropper.vue | 207 +++ .../src/components/cropper/src/typing.ts | 8 + .../src/components/description/index.ts | 3 + .../description/src/description.vue | 205 +++ .../src/components/description/src/typing.ts | 48 + .../description/src/useDescription.ts | 47 + .../web-antd/src/components/dict/index.ts | 2 + .../web-antd/src/components/dict/src/data.tsx | 44 + .../src/components/dict/src/index.vue | 62 + .../web-antd/src/components/global/button.ts | 21 + .../web-antd/src/components/global/index.ts | 14 + .../web-antd/src/components/table/index.ts | 2 + .../src/components/table/src/options-tag.vue | 21 + .../src/components/table/src/table-switch.vue | 134 ++ .../src/components/tenant-toggle/index.ts | 1 + .../components/tenant-toggle/src/index.vue | 163 +++ .../web-antd/src/components/tinymce/index.ts | 1 + .../src/components/tinymce/src/editor.vue | 264 ++++ .../src/components/tinymce/src/tinymce.ts | 11 + .../web-antd/src/components/tree/index.ts | 2 + .../web-antd/src/components/tree/src/data.tsx | 98 ++ .../src/components/tree/src/helper.tsx | 206 +++ .../web-antd/src/components/tree/src/hook.tsx | 62 + .../components/tree/src/menu-select-table.vue | 411 ++++++ .../components/tree/src/tree-select-panel.vue | 222 +++ .../src/components/upload-old/index.ts | 8 + .../components/upload-old/src/file-upload.vue | 240 +++ .../src/components/upload-old/src/helper.ts | 51 + .../upload-old/src/image-upload.vue | 323 ++++ .../src/components/upload-old/src/typing.ts | 37 + .../components/upload-old/src/use-upload.ts | 61 + .../web-antd/src/components/upload/index.ts | 2 + .../src/components/upload/src/file-upload.vue | 150 ++ .../src/components/upload/src/helper.ts | 28 + .../src/components/upload/src/hook.ts | 385 +++++ .../components/upload/src/image-upload.vue | 190 +++ .../src/components/upload/src/note.md | 26 + .../src/components/upload/src/props.d.ts | 122 ++ .../apps/web-antd/src/layouts/auth.vue | 23 + .../apps/web-antd/src/layouts/basic.vue | 168 +++ .../apps/web-antd/src/layouts/index.ts | 6 + .../apps/web-antd/src/locales/README.md | 3 + .../apps/web-antd/src/locales/index.ts | 103 ++ .../src/locales/langs/en-US/component.json | 59 + .../src/locales/langs/en-US/demos.json | 12 + .../src/locales/langs/en-US/http.json | 7 + .../src/locales/langs/en-US/menu.json | 55 + .../src/locales/langs/en-US/page.json | 15 + .../src/locales/langs/en-US/pages.json | 27 + .../src/locales/langs/zh-CN/component.json | 59 + .../src/locales/langs/zh-CN/demos.json | 12 + .../src/locales/langs/zh-CN/http.json | 7 + .../src/locales/langs/zh-CN/menu.json | 55 + .../src/locales/langs/zh-CN/page.json | 15 + .../src/locales/langs/zh-CN/pages.json | 27 + Yi.Vben5.Vue3/apps/web-antd/src/main.ts | 31 + .../apps/web-antd/src/preferences.ts | 72 + .../apps/web-antd/src/router/access.ts | 266 ++++ .../apps/web-antd/src/router/guard.ts | 133 ++ .../apps/web-antd/src/router/index.ts | 37 + .../apps/web-antd/src/router/routes/core.ts | 105 ++ .../apps/web-antd/src/router/routes/index.ts | 41 + .../apps/web-antd/src/router/routes/local.ts | 64 + .../src/router/routes/modules/dashboard.ts | 38 + .../src/router/routes/modules/vben.ts | 90 ++ .../src/router/routes/workflow-iframe.ts | 18 + Yi.Vben5.Vue3/apps/web-antd/src/store/auth.ts | 145 ++ Yi.Vben5.Vue3/apps/web-antd/src/store/dict.ts | 109 ++ .../apps/web-antd/src/store/index.ts | 2 + .../apps/web-antd/src/store/notify.ts | 149 ++ .../apps/web-antd/src/store/tenant.ts | 44 + Yi.Vben5.Vue3/apps/web-antd/src/upload-tip.ts | 36 + Yi.Vben5.Vue3/apps/web-antd/src/utils/dict.ts | 82 ++ .../web-antd/src/utils/encryption/crypto.ts | 80 + .../src/utils/encryption/jsencrypt.ts | 31 + .../web-antd/src/utils/file/base64Conver.ts | 46 + .../apps/web-antd/src/utils/file/download.ts | 268 ++++ .../apps/web-antd/src/utils/file/index.ts | 31 + .../apps/web-antd/src/utils/modal.tsx | 64 + .../apps/web-antd/src/utils/popup.ts | 126 ++ .../apps/web-antd/src/utils/render.tsx | 230 +++ .../apps/web-antd/src/views/_core/README.md | 3 + .../web-antd/src/views/_core/about/index.vue | 9 + .../views/_core/authentication/code-login.vue | 144 ++ .../_core/authentication/forget-password.vue | 42 + .../src/views/_core/authentication/login.vue | 180 +++ .../_core/authentication/oauth-login.vue | 44 + .../_core/authentication/qrcode-login.vue | 10 + .../views/_core/authentication/register.vue | 95 ++ .../src/views/_core/fallback/coming-soon.vue | 7 + .../src/views/_core/fallback/forbidden.vue | 9 + .../views/_core/fallback/internal-error.vue | 9 + .../src/views/_core/fallback/not-found.vue | 9 + .../src/views/_core/fallback/offline.vue | 9 + .../web-antd/src/views/_core/oauth-common.ts | 102 ++ .../_core/profile/components/account-bind.vue | 150 ++ .../_core/profile/components/base-setting.vue | 120 ++ .../profile/components/online-device.vue | 52 + .../profile/components/secure-setting.vue | 106 ++ .../src/views/_core/profile/index.vue | 54 + .../web-antd/src/views/_core/profile/mitt.ts | 7 + .../src/views/_core/profile/profile-panel.vue | 84 ++ .../src/views/_core/profile/setting-panel.vue | 39 + .../src/views/_core/social-callback/index.vue | 84 ++ .../dashboard/analytics/analytics-trends.vue | 97 ++ .../analytics/analytics-visits-data.vue | 81 ++ .../analytics/analytics-visits-sales.vue | 45 + .../analytics/analytics-visits-source.vue | 64 + .../dashboard/analytics/analytics-visits.vue | 54 + .../src/views/dashboard/analytics/index.vue | 90 ++ .../src/views/dashboard/workspace/index.vue | 266 ++++ .../web-antd/src/views/demo/demo/api/index.ts | 60 + .../src/views/demo/demo/api/model.d.ts | 82 ++ .../apps/web-antd/src/views/demo/demo/data.ts | 93 ++ .../src/views/demo/demo/demo-modal.vue | 87 ++ .../web-antd/src/views/demo/demo/index.vue | 165 +++ .../web-antd/src/views/demo/tree/api/index.ts | 50 + .../src/views/demo/tree/api/model.d.ts | 102 ++ .../apps/web-antd/src/views/demo/tree/data.ts | 108 ++ .../web-antd/src/views/demo/tree/index.vue | 146 ++ .../src/views/demo/tree/tree-modal.vue | 104 ++ .../src/views/monitor/admin/index.vue | 7 + .../cache/components/command-chart.vue | 63 + .../views/monitor/cache/components/index.ts | 3 + .../monitor/cache/components/memory-chart.vue | 89 ++ .../cache/components/redis-description.vue | 58 + .../src/views/monitor/cache/index.vue | 107 ++ .../web-antd/src/views/monitor/cache/list.vue | 318 ++++ .../src/views/monitor/logininfor/data.tsx | 87 ++ .../src/views/monitor/logininfor/index.vue | 217 +++ .../monitor/logininfor/login-info-modal.vue | 57 + .../web-antd/src/views/monitor/online/data.ts | 77 + .../src/views/monitor/online/index.vue | 91 ++ .../src/views/monitor/operlog/data.tsx | 67 + .../src/views/monitor/operlog/index.vue | 184 +++ .../operlog/operation-preview-drawer.vue | 80 + .../src/views/monitor/server/index.vue | 216 +++ .../src/views/monitor/snailjob/index.vue | 4 + .../src/views/system/client/client-drawer.vue | 146 ++ .../web-antd/src/views/system/client/data.tsx | 196 +++ .../src/views/system/client/index.vue | 182 +++ .../src/views/system/client/secret-input.vue | 52 + .../src/views/system/config/config-modal.vue | 89 ++ .../web-antd/src/views/system/config/data.ts | 129 ++ .../src/views/system/config/index.vue | 177 +++ .../web-antd/src/views/system/dept/data.ts | 139 ++ .../src/views/system/dept/dept-drawer.vue | 177 +++ .../web-antd/src/views/system/dept/index.vue | 186 +++ .../web-antd/src/views/system/dict/data.vue | 6 + .../src/views/system/dict/data/data.ts | 136 ++ .../system/dict/data/dict-data-drawer.vue | 146 ++ .../src/views/system/dict/data/index.vue | 186 +++ .../system/dict/data/tag-style-picker.vue | 86 ++ .../web-antd/src/views/system/dict/index.vue | 21 + .../web-antd/src/views/system/dict/mitt.ts | 10 + .../src/views/system/dict/type/data.ts | 104 ++ .../system/dict/type/dict-type-modal.vue | 93 ++ .../views/system/dict/type/index-refactor.vue | 281 ++++ .../src/views/system/dict/type/index.vue | 210 +++ .../web-antd/src/views/system/menu/data.tsx | 434 ++++++ .../web-antd/src/views/system/menu/index.vue | 263 ++++ .../src/views/system/menu/menu-drawer.vue | 159 ++ .../web-antd/src/views/system/notice/data.ts | 128 ++ .../src/views/system/notice/index.vue | 148 ++ .../src/views/system/notice/notice-modal.vue | 178 +++ .../src/views/system/oss-config/data.tsx | 223 +++ .../src/views/system/oss-config/index.vue | 166 +++ .../system/oss-config/oss-config-drawer.vue | 115 ++ .../web-antd/src/views/system/oss/config.vue | 11 + .../web-antd/src/views/system/oss/constant.ts | 2 + .../web-antd/src/views/system/oss/data.tsx | 75 + .../src/views/system/oss/fallback-image.txt | 1 + .../views/system/oss/file-upload-modal.vue | 41 + .../views/system/oss/image-upload-modal.vue | 37 + .../web-antd/src/views/system/oss/index.vue | 293 ++++ .../web-antd/src/views/system/post/data.ts | 131 ++ .../web-antd/src/views/system/post/index.vue | 185 +++ .../src/views/system/post/post-drawer.vue | 113 ++ .../src/views/system/role-assign/data.tsx | 43 + .../src/views/system/role-assign/index.vue | 161 ++ .../system/role-assign/role-assign-drawer.vue | 87 ++ .../src/views/system/role/authUser.vue | 11 + .../web-antd/src/views/system/role/data.tsx | 210 +++ .../web-antd/src/views/system/role/index.vue | 228 +++ .../system/role/role-datascope-drawer.vue | 151 ++ .../src/views/system/role/role-drawer.vue | 171 +++ .../web-antd/src/views/system/tenant/data.tsx | 268 ++++ .../src/views/system/tenant/index.vue | 236 +++ .../src/views/system/tenant/tenant-drawer.vue | 134 ++ .../src/views/system/tenantPackage/data.ts | 71 + .../src/views/system/tenantPackage/index.vue | 191 +++ .../tenantPackage/tenant-package-drawer.vue | 154 ++ .../src/views/system/user/authRole.vue | 6 + .../web-antd/src/views/system/user/data.tsx | 210 +++ .../src/views/system/user/dept-tree.vue | 131 ++ .../web-antd/src/views/system/user/index.vue | 298 ++++ .../src/views/system/user/user-drawer.vue | 348 +++++ .../views/system/user/user-import-modal.vue | 108 ++ .../src/views/system/user/user-info-modal.vue | 122 ++ .../system/user/user-reset-pwd-modal.vue | 112 ++ .../src/views/tool/gen/code-preview-modal.vue | 218 +++ .../apps/web-antd/src/views/tool/gen/data.tsx | 60 + .../web-antd/src/views/tool/gen/edit-gen.vue | 120 ++ .../tool/gen/edit-steps/basic-setting.vue | 153 ++ .../src/views/tool/gen/edit-steps/basic.tsx | 212 +++ .../views/tool/gen/edit-steps/gen-config.vue | 91 ++ .../views/tool/gen/edit-steps/gen-data.tsx | 307 ++++ .../src/views/tool/gen/edit-steps/index.ts | 2 + .../web-antd/src/views/tool/gen/editTable.vue | 10 + .../web-antd/src/views/tool/gen/index.vue | 291 ++++ .../src/views/tool/gen/table-import-modal.vue | 145 ++ .../workflow/category/category-modal.vue | 137 ++ .../src/views/workflow/category/data.ts | 69 + .../src/views/workflow/category/index.vue | 153 ++ .../views/workflow/components/apply-modal.vue | 149 ++ .../workflow/components/approval-card.vue | 93 ++ .../workflow/components/approval-content.vue | 26 + .../workflow/components/approval-details.vue | 41 + .../workflow/components/approval-modal.vue | 227 +++ .../workflow/components/approval-panel.vue | 556 +++++++ .../components/approval-rejection-modal.vue | 146 ++ .../components/approval-timeline-item.vue | 81 ++ .../workflow/components/approval-timeline.vue | 21 + .../workflow/components/copy-component.vue | 98 ++ .../workflow/components/flow-designer.vue | 61 + .../workflow/components/flow-info-modal.vue | 38 + .../components/flow-interfere-modal.vue | 171 +++ .../workflow/components/flow-preview.vue | 32 + .../src/views/workflow/components/helper.tsx | 60 + .../src/views/workflow/components/hook.ts | 37 + .../src/views/workflow/components/index.ts | 30 + .../workflow/components/user-select-modal.vue | 378 +++++ .../src/views/workflow/leave/api/index.ts | 62 + .../src/views/workflow/leave/api/model.d.ts | 107 ++ .../src/views/workflow/leave/data.tsx | 175 +++ .../src/views/workflow/leave/index.vue | 200 +++ .../workflow/leave/leave-description.vue | 45 + .../src/views/workflow/leave/leave-form.vue | 192 +++ .../src/views/workflow/leave/leaveEdit.vue | 11 + .../processDefinition/category-tree.vue | 115 ++ .../workflow/processDefinition/constant.ts | 36 + .../views/workflow/processDefinition/data.tsx | 103 ++ .../workflow/processDefinition/design.vue | 11 + .../workflow/processDefinition/index.vue | 378 +++++ .../process-definition-deploy-modal.vue | 73 + .../process-definition-modal.vue | 137 ++ .../views/workflow/processInstance/data.tsx | 91 ++ .../views/workflow/processInstance/index.vue | 240 +++ .../instance-invalid-modal.vue | 67 + .../instance-variable-modal.vue | 28 + .../views/workflow/task/allTaskWaiting.vue | 360 +++++ .../src/views/workflow/task/constant.ts | 7 + .../src/views/workflow/task/myDocument.vue | 257 ++++ .../src/views/workflow/task/taskCopyList.vue | 299 ++++ .../src/views/workflow/task/taskFinish.vue | 299 ++++ .../src/views/workflow/task/taskWaiting.vue | 302 ++++ .../apps/web-antd/tailwind.config.mjs | 1 + Yi.Vben5.Vue3/apps/web-antd/tsconfig.json | 12 + .../apps/web-antd/tsconfig.node.json | 10 + .../apps/web-antd/types/directive.d.ts | 12 + .../web-antd/types/global-components.d.ts | 9 + Yi.Vben5.Vue3/apps/web-antd/vite.config.mts | 37 + Yi.Vben5.Vue3/cspell.json | 77 + .../.vitepress/components/demo-preview.vue | 45 + .../docs/.vitepress/components/index.ts | 1 + .../.vitepress/components/preview-group.vue | 110 ++ Yi.Vben5.Vue3/docs/.vitepress/config/en.mts | 231 +++ .../docs/.vitepress/config/index.mts | 25 + .../.vitepress/config/plugins/demo-preview.ts | 143 ++ .../docs/.vitepress/config/shared.mts | 172 +++ Yi.Vben5.Vue3/docs/.vitepress/config/zh.mts | 358 +++++ .../theme/components/site-layout.vue | 96 ++ .../theme/components/vben-contributors.vue | 29 + Yi.Vben5.Vue3/docs/.vitepress/theme/index.ts | 29 + .../docs/.vitepress/theme/plugins/hm.ts | 28 + .../docs/.vitepress/theme/styles/base.css | 22 + .../docs/.vitepress/theme/styles/index.ts | 4 + .../.vitepress/theme/styles/variables.css | 132 ++ Yi.Vben5.Vue3/docs/package.json | 35 + .../docs/src/_env/adapter/component.ts | 125 ++ Yi.Vben5.Vue3/docs/src/_env/adapter/form.ts | 47 + .../docs/src/_env/adapter/vxe-table.ts | 70 + .../docs/src/_env/node/adapter/form.ts | 4 + .../docs/src/_env/node/adapter/vxe-table.ts | 3 + .../docs/src/commercial/community.md | 30 + .../docs/src/commercial/customized.md | 12 + .../docs/src/commercial/technical-support.md | 8 + .../src/components/common-ui/vben-alert.md | 166 +++ .../common-ui/vben-api-component.md | 173 +++ .../common-ui/vben-count-to-animator.md | 59 + .../src/components/common-ui/vben-drawer.md | 156 ++ .../common-ui/vben-ellipsis-text.md | 64 + .../src/components/common-ui/vben-form.md | 560 +++++++ .../src/components/common-ui/vben-modal.md | 164 +++ .../components/common-ui/vben-vxe-table.md | 276 ++++ .../docs/src/components/introduction.md | 15 + .../docs/src/components/layout-ui/page.md | 44 + .../docs/src/demos/vben-alert/alert/index.vue | 36 + .../src/demos/vben-alert/confirm/index.vue | 75 + .../src/demos/vben-alert/prompt/index.vue | 118 ++ .../vben-api-component/cascader/index.vue | 100 ++ .../vben-count-to-animator/basic/index.vue | 6 + .../vben-count-to-animator/custom/index.vue | 12 + .../demos/vben-drawer/auto-height/drawer.vue | 45 + .../demos/vben-drawer/auto-height/index.vue | 21 + .../src/demos/vben-drawer/basic/index.vue | 11 + .../src/demos/vben-drawer/dynamic/drawer.vue | 26 + .../src/demos/vben-drawer/dynamic/index.vue | 29 + .../src/demos/vben-drawer/extra/drawer.vue | 8 + .../src/demos/vben-drawer/extra/index.vue | 21 + .../demos/vben-drawer/shared-data/drawer.vue | 26 + .../demos/vben-drawer/shared-data/index.vue | 27 + .../vben-ellipsis-text/auto-display/index.vue | 16 + .../demos/vben-ellipsis-text/expand/index.vue | 10 + .../demos/vben-ellipsis-text/line/index.vue | 10 + .../vben-ellipsis-text/tooltip/index.vue | 14 + .../docs/src/demos/vben-form/api/index.vue | 236 +++ .../docs/src/demos/vben-form/basic/index.vue | 231 +++ .../docs/src/demos/vben-form/custom/index.vue | 68 + .../src/demos/vben-form/dynamic/index.vue | 168 +++ .../docs/src/demos/vben-form/query/index.vue | 94 ++ .../docs/src/demos/vben-form/rules/index.vue | 189 +++ .../demos/vben-modal/auto-height/index.vue | 21 + .../demos/vben-modal/auto-height/modal.vue | 45 + .../docs/src/demos/vben-modal/basic/index.vue | 11 + .../src/demos/vben-modal/draggable/index.vue | 21 + .../src/demos/vben-modal/draggable/modal.vue | 10 + .../src/demos/vben-modal/dynamic/index.vue | 29 + .../src/demos/vben-modal/dynamic/modal.vue | 38 + .../docs/src/demos/vben-modal/extra/index.vue | 21 + .../docs/src/demos/vben-modal/extra/modal.vue | 8 + .../demos/vben-modal/shared-data/index.vue | 27 + .../demos/vben-modal/shared-data/modal.vue | 26 + .../src/demos/vben-vxe-table/basic/index.vue | 85 ++ .../vben-vxe-table/custom-cell/index.vue | 105 ++ .../demos/vben-vxe-table/edit-cell/index.vue | 55 + .../demos/vben-vxe-table/edit-row/index.vue | 92 ++ .../src/demos/vben-vxe-table/fixed/index.vue | 67 + .../src/demos/vben-vxe-table/form/index.vue | 127 ++ .../docs/src/demos/vben-vxe-table/mock-api.ts | 36 + .../src/demos/vben-vxe-table/remote/index.vue | 112 ++ .../src/demos/vben-vxe-table/table-data.ts | 384 +++++ .../src/demos/vben-vxe-table/tree/index.vue | 80 + .../demos/vben-vxe-table/virtual/index.vue | 64 + .../docs/src/en/guide/essentials/build.md | 243 ++++ .../docs/src/en/guide/essentials/concept.md | 70 + .../src/en/guide/essentials/development.md | 255 ++++ .../en/guide/essentials/external-module.md | 58 + .../docs/src/en/guide/essentials/icons.md | 78 + .../docs/src/en/guide/essentials/route.md | 603 ++++++++ .../docs/src/en/guide/essentials/server.md | 356 +++++ .../docs/src/en/guide/essentials/settings.md | 626 ++++++++ .../docs/src/en/guide/essentials/styles.md | 106 ++ .../docs/src/en/guide/in-depth/access.md | 356 +++++ .../src/en/guide/in-depth/check-updates.md | 48 + .../docs/src/en/guide/in-depth/features.md | 84 ++ .../docs/src/en/guide/in-depth/layout.md | 1 + .../docs/src/en/guide/in-depth/loading.md | 44 + .../docs/src/en/guide/in-depth/locale.md | 227 +++ .../docs/src/en/guide/in-depth/login.md | 119 ++ .../docs/src/en/guide/in-depth/theme.md | 1293 +++++++++++++++++ .../src/en/guide/in-depth/ui-framework.md | 17 + .../src/en/guide/introduction/changelog.md | 3 + .../src/en/guide/introduction/quick-start.md | 95 ++ .../docs/src/en/guide/introduction/roadmap.md | 3 + .../docs/src/en/guide/introduction/thin.md | 67 + .../docs/src/en/guide/introduction/vben.md | 49 + .../docs/src/en/guide/introduction/why.md | 9 + Yi.Vben5.Vue3/docs/src/en/guide/other/faq.md | 159 ++ .../docs/src/en/guide/other/project-update.md | 54 + .../docs/src/en/guide/other/remove-code.md | 18 + .../docs/src/en/guide/project/changeset.md | 21 + .../docs/src/en/guide/project/cli.md | 106 ++ .../docs/src/en/guide/project/dir.md | 68 + .../docs/src/en/guide/project/standard.md | 213 +++ .../docs/src/en/guide/project/tailwindcss.md | 13 + .../docs/src/en/guide/project/test.md | 33 + .../docs/src/en/guide/project/vite.md | 33 + Yi.Vben5.Vue3/docs/src/en/index.md | 76 + Yi.Vben5.Vue3/docs/src/friend-links/index.md | 27 + .../docs/src/guide/essentials/build.md | 243 ++++ .../docs/src/guide/essentials/concept.md | 70 + .../docs/src/guide/essentials/development.md | 255 ++++ .../src/guide/essentials/external-module.md | 58 + .../docs/src/guide/essentials/icons.md | 78 + .../docs/src/guide/essentials/route.md | 644 ++++++++ .../docs/src/guide/essentials/server.md | 387 +++++ .../docs/src/guide/essentials/settings.md | 629 ++++++++ .../docs/src/guide/essentials/styles.md | 106 ++ .../docs/src/guide/in-depth/access.md | 357 +++++ .../docs/src/guide/in-depth/check-updates.md | 92 ++ .../docs/src/guide/in-depth/features.md | 84 ++ .../docs/src/guide/in-depth/layout.md | 1 + .../docs/src/guide/in-depth/loading.md | 46 + .../docs/src/guide/in-depth/locale.md | 227 +++ .../docs/src/guide/in-depth/login.md | 220 +++ .../docs/src/guide/in-depth/theme.md | 1293 +++++++++++++++++ .../docs/src/guide/in-depth/ui-framework.md | 17 + .../docs/src/guide/introduction/changelog.md | 3 + .../src/guide/introduction/quick-start.md | 111 ++ .../docs/src/guide/introduction/roadmap.md | 3 + .../docs/src/guide/introduction/thin.md | 94 ++ .../docs/src/guide/introduction/vben.md | 49 + .../docs/src/guide/introduction/why.md | 23 + Yi.Vben5.Vue3/docs/src/guide/other/faq.md | 159 ++ .../docs/src/guide/other/project-update.md | 55 + .../docs/src/guide/other/remove-code.md | 18 + .../docs/src/guide/project/changeset.md | 21 + Yi.Vben5.Vue3/docs/src/guide/project/cli.md | 113 ++ Yi.Vben5.Vue3/docs/src/guide/project/dir.md | 68 + .../docs/src/guide/project/standard.md | 213 +++ .../docs/src/guide/project/tailwindcss.md | 17 + Yi.Vben5.Vue3/docs/src/guide/project/test.md | 33 + Yi.Vben5.Vue3/docs/src/guide/project/vite.md | 33 + Yi.Vben5.Vue3/docs/src/index.md | 111 ++ Yi.Vben5.Vue3/docs/src/sponsor/personal.md | 12 + Yi.Vben5.Vue3/docs/tailwind.config.mjs | 11 + Yi.Vben5.Vue3/docs/tsconfig.json | 19 + Yi.Vben5.Vue3/eslint.config.mjs | 9 + .../lint-configs/commitlint-config/index.mjs | 154 ++ .../commitlint-config/package.json | 33 + .../eslint-config/build.config.ts | 7 + .../lint-configs/eslint-config/package.json | 56 + .../eslint-config/src/configs/command.ts | 10 + .../eslint-config/src/configs/comments.ts | 24 + .../eslint-config/src/configs/disableds.ts | 28 + .../eslint-config/src/configs/ignores.ts | 52 + .../eslint-config/src/configs/import.ts | 25 + .../eslint-config/src/configs/index.ts | 17 + .../eslint-config/src/configs/javascript.ts | 241 +++ .../eslint-config/src/configs/jsdoc.ts | 34 + .../eslint-config/src/configs/jsonc.ts | 258 ++++ .../eslint-config/src/configs/node.ts | 57 + .../src/configs/perfectionist.ts | 89 ++ .../eslint-config/src/configs/prettier.ts | 19 + .../eslint-config/src/configs/regexp.ts | 20 + .../eslint-config/src/configs/test.ts | 45 + .../eslint-config/src/configs/turbo.ts | 18 + .../eslint-config/src/configs/typescript.ts | 72 + .../eslint-config/src/configs/unicorn.ts | 45 + .../eslint-config/src/configs/vue.ts | 153 ++ .../eslint-config/src/custom-config.ts | 172 +++ .../lint-configs/eslint-config/src/index.ts | 60 + .../lint-configs/eslint-config/src/util.ts | 8 + .../lint-configs/eslint-config/tsconfig.json | 6 + .../lint-configs/prettier-config/index.mjs | 18 + .../lint-configs/prettier-config/package.json | 28 + .../lint-configs/stylelint-config/index.mjs | 141 ++ .../stylelint-config/package.json | 43 + .../internal/node-utils/build.config.ts | 7 + .../internal/node-utils/package.json | 43 + .../node-utils/src/__tests__/hash.test.ts | 52 + .../node-utils/src/__tests__/path.test.ts | 67 + .../internal/node-utils/src/constants.ts | 6 + Yi.Vben5.Vue3/internal/node-utils/src/date.ts | 12 + Yi.Vben5.Vue3/internal/node-utils/src/fs.ts | 39 + Yi.Vben5.Vue3/internal/node-utils/src/git.ts | 34 + Yi.Vben5.Vue3/internal/node-utils/src/hash.ts | 18 + .../internal/node-utils/src/index.ts | 19 + .../internal/node-utils/src/monorepo.ts | 46 + Yi.Vben5.Vue3/internal/node-utils/src/path.ts | 11 + .../internal/node-utils/src/prettier.ts | 21 + .../internal/node-utils/src/spinner.ts | 26 + .../internal/node-utils/tsconfig.json | 6 + .../internal/tailwind-config/build.config.ts | 10 + .../internal/tailwind-config/package.json | 66 + .../internal/tailwind-config/src/index.ts | 266 ++++ .../internal/tailwind-config/src/module.d.ts | 3 + .../tailwind-config/src/plugins/entry.ts | 53 + .../tailwind-config/src/postcss.config.ts | 15 + .../internal/tailwind-config/tsconfig.json | 9 + Yi.Vben5.Vue3/internal/tsconfig/base.json | 40 + Yi.Vben5.Vue3/internal/tsconfig/library.json | 13 + Yi.Vben5.Vue3/internal/tsconfig/node.json | 12 + Yi.Vben5.Vue3/internal/tsconfig/package.json | 25 + Yi.Vben5.Vue3/internal/tsconfig/web-app.json | 8 + Yi.Vben5.Vue3/internal/tsconfig/web.json | 14 + .../internal/vite-config/build.config.ts | 7 + .../internal/vite-config/package.json | 59 + .../vite-config/src/config/application.ts | 125 ++ .../internal/vite-config/src/config/common.ts | 13 + .../internal/vite-config/src/config/index.ts | 37 + .../vite-config/src/config/library.ts | 59 + .../internal/vite-config/src/index.ts | 4 + .../internal/vite-config/src/options.ts | 45 + .../vite-config/src/plugins/archiver.ts | 75 + .../src/plugins/extra-app-config.ts | 92 ++ .../vite-config/src/plugins/importmap.ts | 245 ++++ .../internal/vite-config/src/plugins/index.ts | 247 ++++ .../src/plugins/inject-app-loading/README.md | 3 + .../default-loading-antd.html | 107 ++ .../inject-app-loading/default-loading.html | 113 ++ .../src/plugins/inject-app-loading/index.ts | 66 + .../src/plugins/inject-metadata.ts | 111 ++ .../vite-config/src/plugins/license.ts | 63 + .../vite-config/src/plugins/nitro-mock.ts | 98 ++ .../internal/vite-config/src/plugins/print.ts | 28 + .../vite-config/src/plugins/vxe-table.ts | 20 + .../internal/vite-config/src/typing.ts | 343 +++++ .../internal/vite-config/src/utils/env.ts | 109 ++ .../internal/vite-config/tsconfig.json | 6 + Yi.Vben5.Vue3/lefthook.yml | 42 + Yi.Vben5.Vue3/package.json | 118 ++ Yi.Vben5.Vue3/playground/.env | 8 + Yi.Vben5.Vue3/playground/.env.analyze | 7 + Yi.Vben5.Vue3/playground/.env.development | 16 + Yi.Vben5.Vue3/playground/.env.production | 19 + .../__tests__/e2e/auth-login.spec.ts | 20 + .../playground/__tests__/e2e/common/auth.ts | 46 + Yi.Vben5.Vue3/playground/index.html | 35 + Yi.Vben5.Vue3/playground/package.json | 59 + Yi.Vben5.Vue3/playground/playwright.config.ts | 108 ++ Yi.Vben5.Vue3/playground/postcss.config.mjs | 1 + .../playground/src/adapter/component/index.ts | 205 +++ Yi.Vben5.Vue3/playground/src/adapter/form.ts | 47 + .../playground/src/adapter/vxe-table.ts | 297 ++++ Yi.Vben5.Vue3/playground/src/api/core/auth.ts | 57 + .../playground/src/api/core/index.ts | 3 + Yi.Vben5.Vue3/playground/src/api/core/menu.ts | 10 + Yi.Vben5.Vue3/playground/src/api/core/user.ts | 10 + .../playground/src/api/examples/download.ts | 28 + .../playground/src/api/examples/index.ts | 2 + .../src/api/examples/json-bigint.ts | 10 + .../playground/src/api/examples/params.ts | 19 + .../playground/src/api/examples/status.ts | 10 + .../playground/src/api/examples/table.ts | 18 + .../playground/src/api/examples/upload.ts | 25 + Yi.Vben5.Vue3/playground/src/api/index.ts | 3 + Yi.Vben5.Vue3/playground/src/api/request.ts | 129 ++ .../playground/src/api/system/dept.ts | 54 + .../playground/src/api/system/index.ts | 3 + .../playground/src/api/system/menu.ts | 158 ++ .../playground/src/api/system/role.ts | 55 + Yi.Vben5.Vue3/playground/src/app.vue | 39 + Yi.Vben5.Vue3/playground/src/bootstrap.ts | 80 + Yi.Vben5.Vue3/playground/src/layouts/auth.vue | 25 + .../playground/src/layouts/basic.vue | 183 +++ Yi.Vben5.Vue3/playground/src/layouts/index.ts | 6 + .../playground/src/locales/README.md | 3 + Yi.Vben5.Vue3/playground/src/locales/index.ts | 98 ++ .../src/locales/langs/en-US/demos.json | 70 + .../src/locales/langs/en-US/examples.json | 74 + .../src/locales/langs/en-US/page.json | 16 + .../src/locales/langs/en-US/system.json | 65 + .../src/locales/langs/zh-CN/demos.json | 71 + .../src/locales/langs/zh-CN/examples.json | 74 + .../src/locales/langs/zh-CN/page.json | 16 + .../src/locales/langs/zh-CN/system.json | 67 + Yi.Vben5.Vue3/playground/src/main.ts | 31 + Yi.Vben5.Vue3/playground/src/preferences.ts | 13 + Yi.Vben5.Vue3/playground/src/router/access.ts | 42 + Yi.Vben5.Vue3/playground/src/router/guard.ts | 136 ++ Yi.Vben5.Vue3/playground/src/router/index.ts | 37 + .../playground/src/router/routes/core.ts | 97 ++ .../playground/src/router/routes/index.ts | 47 + .../src/router/routes/modules/dashboard.ts | 38 + .../src/router/routes/modules/demos.ts | 594 ++++++++ .../src/router/routes/modules/examples.ts | 317 ++++ .../src/router/routes/modules/system.ts | 46 + .../src/router/routes/modules/vben.ts | 94 ++ Yi.Vben5.Vue3/playground/src/store/auth.ts | 120 ++ Yi.Vben5.Vue3/playground/src/store/index.ts | 1 + .../playground/src/views/_core/README.md | 3 + .../src/views/_core/about/index.vue | 9 + .../views/_core/authentication/code-login.vue | 109 ++ .../_core/authentication/forget-password.vue | 42 + .../src/views/_core/authentication/login.vue | 132 ++ .../_core/authentication/qrcode-login.vue | 10 + .../views/_core/authentication/register.vue | 96 ++ .../src/views/_core/fallback/coming-soon.vue | 7 + .../src/views/_core/fallback/forbidden.vue | 9 + .../views/_core/fallback/internal-error.vue | 9 + .../src/views/_core/fallback/not-found.vue | 9 + .../src/views/_core/fallback/offline.vue | 9 + .../dashboard/analytics/analytics-trends.vue | 97 ++ .../analytics/analytics-visits-data.vue | 81 ++ .../analytics/analytics-visits-sales.vue | 45 + .../analytics/analytics-visits-source.vue | 64 + .../dashboard/analytics/analytics-visits.vue | 54 + .../src/views/dashboard/analytics/index.vue | 90 ++ .../src/views/dashboard/workspace/index.vue | 266 ++++ .../src/views/demos/access/admin-visible.vue | 11 + .../src/views/demos/access/button-control.vue | 155 ++ .../src/views/demos/access/index.vue | 93 ++ .../views/demos/access/menu-visible-403.vue | 11 + .../src/views/demos/access/super-visible.vue | 11 + .../src/views/demos/access/user-visible.vue | 11 + .../src/views/demos/active-icon/index.vue | 11 + .../src/views/demos/badge/index.vue | 113 ++ .../views/demos/breadcrumb/lateral-detail.vue | 21 + .../src/views/demos/breadcrumb/lateral.vue | 25 + .../views/demos/breadcrumb/level-detail.vue | 11 + .../views/demos/features/clipboard/index.vue | 25 + .../demos/features/file-download/base64.ts | 1 + .../demos/features/file-download/index.vue | 100 ++ .../demos/features/full-screen/index.vue | 47 + .../features/hide-menu-children/children.vue | 23 + .../features/hide-menu-children/parent.vue | 17 + .../src/views/demos/features/icons/index.vue | 115 ++ .../demos/features/json-bigint/index.vue | 39 + .../demos/features/login-expired/index.vue | 39 + .../views/demos/features/menu-query/index.vue | 11 + .../views/demos/features/new-window/index.vue | 11 + .../request-params-serializer/index.vue | 61 + .../src/views/demos/features/tabs/index.vue | 105 ++ .../views/demos/features/tabs/tab-detail.vue | 23 + .../vue-query/concurrency-caching.vue | 61 + .../views/demos/features/vue-query/index.vue | 40 + .../features/vue-query/infinite-queries.vue | 58 + .../features/vue-query/paginated-queries.vue | 52 + .../features/vue-query/query-retries.vue | 34 + .../views/demos/features/vue-query/typing.ts | 18 + .../views/demos/features/watermark/index.vue | 86 ++ .../src/views/demos/nested/menu-1.vue | 7 + .../src/views/demos/nested/menu-2-1.vue | 7 + .../src/views/demos/nested/menu-3-1.vue | 7 + .../src/views/demos/nested/menu-3-2-1.vue | 7 + .../src/views/examples/button-group/index.vue | 229 +++ .../captcha/point-selection-captcha.vue | 181 +++ .../views/examples/captcha/slider-captcha.vue | 117 ++ .../captcha/slider-rotate-captcha.vue | 28 + .../src/views/examples/count-to/index.vue | 178 +++ .../src/views/examples/doc-button.vue | 22 + .../examples/drawer/auto-height-demo.vue | 47 + .../src/views/examples/drawer/base-demo.vue | 35 + .../views/examples/drawer/dynamic-demo.vue | 31 + .../examples/drawer/form-drawer-demo.vue | 56 + .../views/examples/drawer/in-content-demo.vue | 48 + .../src/views/examples/drawer/index.vue | 195 +++ .../examples/drawer/shared-data-demo.vue | 29 + .../src/views/examples/ellipsis/index.vue | 46 + .../src/views/examples/form/api.vue | 274 ++++ .../src/views/examples/form/basic.vue | 447 ++++++ .../src/views/examples/form/custom-layout.vue | 111 ++ .../src/views/examples/form/custom.vue | 100 ++ .../src/views/examples/form/dynamic.vue | 262 ++++ .../src/views/examples/form/merge.vue | 116 ++ .../examples/form/modules/two-fields.vue | 42 + .../src/views/examples/form/query.vue | 147 ++ .../src/views/examples/form/rules.vue | 245 ++++ .../src/views/examples/json-viewer/data.ts | 66 + .../src/views/examples/json-viewer/index.vue | 51 + .../src/views/examples/layout/col-page.vue | 106 ++ .../src/views/examples/loading/index.vue | 101 ++ .../views/examples/modal/auto-height-demo.vue | 49 + .../src/views/examples/modal/base-demo.vue | 34 + .../src/views/examples/modal/blur-demo.vue | 23 + .../src/views/examples/modal/drag-demo.vue | 19 + .../src/views/examples/modal/dynamic-demo.vue | 41 + .../views/examples/modal/form-modal-demo.vue | 91 ++ .../views/examples/modal/in-content-demo.vue | 30 + .../src/views/examples/modal/index.vue | 278 ++++ .../src/views/examples/modal/nested-demo.vue | 24 + .../views/examples/modal/shared-data-demo.vue | 29 + .../src/views/examples/motion/index.vue | 213 +++ .../src/views/examples/resize/basic.vue | 58 + .../src/views/examples/tippy/index.vue | 303 ++++ .../src/views/examples/vxe-table/basic.vue | 111 ++ .../views/examples/vxe-table/custom-cell.vue | 108 ++ .../views/examples/vxe-table/edit-cell.vue | 57 + .../src/views/examples/vxe-table/edit-row.vue | 94 ++ .../src/views/examples/vxe-table/fixed.vue | 69 + .../src/views/examples/vxe-table/form.vue | 127 ++ .../src/views/examples/vxe-table/remote.vue | 81 ++ .../views/examples/vxe-table/table-data.ts | 172 +++ .../src/views/examples/vxe-table/tree.vue | 62 + .../src/views/examples/vxe-table/virtual.vue | 66 + .../playground/src/views/system/dept/data.ts | 136 ++ .../playground/src/views/system/dept/list.vue | 143 ++ .../src/views/system/dept/modules/form.vue | 78 + .../playground/src/views/system/menu/data.ts | 109 ++ .../playground/src/views/system/menu/list.vue | 162 +++ .../src/views/system/menu/modules/form.vue | 505 +++++++ .../playground/src/views/system/role/data.ts | 127 ++ .../playground/src/views/system/role/list.vue | 164 +++ .../src/views/system/role/modules/form.vue | 139 ++ Yi.Vben5.Vue3/playground/tailwind.config.mjs | 1 + Yi.Vben5.Vue3/playground/tsconfig.json | 12 + Yi.Vben5.Vue3/playground/tsconfig.node.json | 10 + Yi.Vben5.Vue3/playground/vite.config.mts | 20 + Yi.Vben5.Vue3/pnpm-workspace.yaml | 193 +++ .../resource/image-20260101175759249.png | Bin 0 -> 101562 bytes .../resource/image-20260101175912025.png | Bin 0 -> 86219 bytes .../resource/image-20260101180006771.png | Bin 0 -> 148288 bytes Yi.Vben5.Vue3/scripts/clean.mjs | 56 + Yi.Vben5.Vue3/scripts/deploy/Dockerfile | 37 + .../deploy/build-local-docker-image.sh | 55 + Yi.Vben5.Vue3/scripts/deploy/nginx.conf | 75 + Yi.Vben5.Vue3/scripts/turbo-run/README.md | 59 + .../scripts/turbo-run/build.config.ts | 7 + Yi.Vben5.Vue3/scripts/turbo-run/package.json | 29 + Yi.Vben5.Vue3/scripts/turbo-run/src/index.ts | 29 + Yi.Vben5.Vue3/scripts/turbo-run/src/run.ts | 67 + Yi.Vben5.Vue3/scripts/turbo-run/tsconfig.json | 6 + Yi.Vben5.Vue3/scripts/vsh/README.md | 56 + Yi.Vben5.Vue3/scripts/vsh/build.config.ts | 7 + Yi.Vben5.Vue3/scripts/vsh/package.json | 31 + .../scripts/vsh/src/check-circular/index.ts | 170 +++ .../scripts/vsh/src/check-dep/index.ts | 194 +++ .../scripts/vsh/src/code-workspace/index.ts | 78 + Yi.Vben5.Vue3/scripts/vsh/src/index.ts | 74 + Yi.Vben5.Vue3/scripts/vsh/src/lint/index.ts | 48 + .../scripts/vsh/src/publint/index.ts | 184 +++ Yi.Vben5.Vue3/scripts/vsh/tsconfig.json | 6 + .../scripts/菜单图标替换sql/update_icon.sql | 54 + Yi.Vben5.Vue3/stylelint.config.mjs | 4 + Yi.Vben5.Vue3/tea.yaml | 6 + Yi.Vben5.Vue3/turbo.json | 49 + Yi.Vben5.Vue3/vben-admin.code-workspace | 164 +++ Yi.Vben5.Vue3/vitest.config.ts | 11 + Yi.Vben5.Vue3/vitest.workspace.ts | 3 + 839 files changed, 74231 insertions(+) create mode 100644 Yi.Vben5.Vue3/.browserslistrc create mode 100644 Yi.Vben5.Vue3/.changeset/README.md create mode 100644 Yi.Vben5.Vue3/.changeset/config.json create mode 100644 Yi.Vben5.Vue3/.commitlintrc.js create mode 100644 Yi.Vben5.Vue3/.dockerignore create mode 100644 Yi.Vben5.Vue3/.editorconfig create mode 100644 Yi.Vben5.Vue3/.gitattributes create mode 100644 Yi.Vben5.Vue3/.gitconfig create mode 100644 Yi.Vben5.Vue3/.gitignore create mode 100644 Yi.Vben5.Vue3/.gitpod.yml create mode 100644 Yi.Vben5.Vue3/.node-version create mode 100644 Yi.Vben5.Vue3/.npmrc create mode 100644 Yi.Vben5.Vue3/.prettierignore create mode 100644 Yi.Vben5.Vue3/.prettierrc.mjs create mode 100644 Yi.Vben5.Vue3/.stylelintignore create mode 100644 Yi.Vben5.Vue3/CHANGELOG.md create mode 100644 Yi.Vben5.Vue3/LICENSE create mode 100644 Yi.Vben5.Vue3/README.md create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/.env create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/README.md create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/status.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].delete.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/name-exists.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/path-exists.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/error.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/package.json create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts create mode 100644 Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/.env create mode 100644 Yi.Vben5.Vue3/apps/web-antd/.env.analyze create mode 100644 Yi.Vben5.Vue3/apps/web-antd/.env.development create mode 100644 Yi.Vben5.Vue3/apps/web-antd/.env.production create mode 100644 Yi.Vben5.Vue3/apps/web-antd/.env.test create mode 100644 Yi.Vben5.Vue3/apps/web-antd/index.html create mode 100644 Yi.Vben5.Vue3/apps/web-antd/package.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/app.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/bootstrap.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-avatar.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/typing.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/description/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/description.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/typing.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/useDescription.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/dict/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/global/button.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/global/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/table/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/options-tag.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/table-switch.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/src/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/editor.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/tinymce.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tree/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/helper.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/hook.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/menu-select-table.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/tree-select-panel.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/file-upload.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/helper.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/image-upload.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/typing.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/use-upload.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/file-upload.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/helper.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/hook.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/image-upload.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/note.md create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/props.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/layouts/auth.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/layouts/basic.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/layouts/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/README.md create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/component.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/demos.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/http.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/menu.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/page.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/pages.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/component.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/demos.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/http.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/menu.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/page.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/pages.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/main.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/preferences.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/access.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/guard.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/routes/core.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/routes/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/routes/local.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/dashboard.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/vben.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/router/routes/workflow-iframe.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/store/auth.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/store/dict.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/store/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/store/notify.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/store/tenant.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/upload-tip.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/dict.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/crypto.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/jsencrypt.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/file/base64Conver.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/file/download.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/file/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/modal.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/popup.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/utils/render.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/README.md create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/about/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/code-login.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/forget-password.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/login.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/oauth-login.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/qrcode-login.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/register.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/coming-soon.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/forbidden.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/internal-error.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/not-found.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/offline.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/oauth-common.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/account-bind.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/base-setting.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/online-device.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/secure-setting.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/mitt.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/profile-panel.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/setting-panel.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/_core/social-callback/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/workspace/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/demo-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/tree-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/admin/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/command-chart.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/memory-chart.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/redis-description.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/list.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/logininfor/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/logininfor/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/logininfor/login-info-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/online/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/online/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/operlog/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/operlog/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/operlog/operation-preview-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/server/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/snailjob/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/client/client-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/client/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/client/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/client/secret-input.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/config/config-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/config/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/config/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dept/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dept/dept-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dept/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/data.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/data/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/data/dict-data-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/data/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/data/tag-style-picker.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/mitt.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/type/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/type/dict-type-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/type/index-refactor.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/dict/type/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/menu/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/menu/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/menu/menu-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/notice/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/notice/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/notice/notice-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss-config/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss-config/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss-config/oss-config-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/config.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/constant.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/fallback-image.txt create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/file-upload-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/image-upload-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/oss/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/post/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/post/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/post/post-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role-assign/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role-assign/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role-assign/role-assign-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role/authUser.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role/role-datascope-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/role/role-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/tenant/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/tenant/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/tenant/tenant-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/tenantPackage/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/tenantPackage/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/tenantPackage/tenant-package-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/authRole.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/dept-tree.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/user-drawer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/user-import-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/user-info-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/system/user/user-reset-pwd-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/code-preview-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/edit-gen.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/edit-steps/basic-setting.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/edit-steps/basic.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/edit-steps/gen-config.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/edit-steps/gen-data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/edit-steps/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/editTable.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/tool/gen/table-import-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/category/category-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/category/data.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/category/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/apply-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-card.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-content.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-details.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-panel.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-rejection-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-timeline-item.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/approval-timeline.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/copy-component.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/flow-designer.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/flow-info-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/flow-interfere-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/flow-preview.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/helper.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/hook.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/components/user-select-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/api/index.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/api/model.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/leave-description.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/leave-form.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/leave/leaveEdit.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/category-tree.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/constant.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/design.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/process-definition-deploy-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processDefinition/process-definition-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processInstance/data.tsx create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processInstance/index.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processInstance/instance-invalid-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/processInstance/instance-variable-modal.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/task/allTaskWaiting.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/task/constant.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/task/myDocument.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/task/taskCopyList.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/task/taskFinish.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/src/views/workflow/task/taskWaiting.vue create mode 100644 Yi.Vben5.Vue3/apps/web-antd/tailwind.config.mjs create mode 100644 Yi.Vben5.Vue3/apps/web-antd/tsconfig.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/tsconfig.node.json create mode 100644 Yi.Vben5.Vue3/apps/web-antd/types/directive.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/types/global-components.d.ts create mode 100644 Yi.Vben5.Vue3/apps/web-antd/vite.config.mts create mode 100644 Yi.Vben5.Vue3/cspell.json create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/components/demo-preview.vue create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/components/index.ts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/components/preview-group.vue create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/config/en.mts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/config/index.mts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/config/plugins/demo-preview.ts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/config/shared.mts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/config/zh.mts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/components/site-layout.vue create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/components/vben-contributors.vue create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/index.ts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/plugins/hm.ts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/styles/base.css create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/styles/index.ts create mode 100644 Yi.Vben5.Vue3/docs/.vitepress/theme/styles/variables.css create mode 100644 Yi.Vben5.Vue3/docs/package.json create mode 100644 Yi.Vben5.Vue3/docs/src/_env/adapter/component.ts create mode 100644 Yi.Vben5.Vue3/docs/src/_env/adapter/form.ts create mode 100644 Yi.Vben5.Vue3/docs/src/_env/adapter/vxe-table.ts create mode 100644 Yi.Vben5.Vue3/docs/src/_env/node/adapter/form.ts create mode 100644 Yi.Vben5.Vue3/docs/src/_env/node/adapter/vxe-table.ts create mode 100644 Yi.Vben5.Vue3/docs/src/commercial/community.md create mode 100644 Yi.Vben5.Vue3/docs/src/commercial/customized.md create mode 100644 Yi.Vben5.Vue3/docs/src/commercial/technical-support.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-alert.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-api-component.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-count-to-animator.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-drawer.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-ellipsis-text.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-form.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-modal.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/common-ui/vben-vxe-table.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/introduction.md create mode 100644 Yi.Vben5.Vue3/docs/src/components/layout-ui/page.md create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-alert/alert/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-alert/confirm/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-alert/prompt/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-api-component/cascader/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-count-to-animator/basic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-count-to-animator/custom/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/auto-height/drawer.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/auto-height/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/basic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/dynamic/drawer.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/dynamic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/extra/drawer.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/extra/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/shared-data/drawer.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-drawer/shared-data/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-ellipsis-text/auto-display/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-ellipsis-text/expand/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-ellipsis-text/line/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-ellipsis-text/tooltip/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-form/api/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-form/basic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-form/custom/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-form/dynamic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-form/query/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-form/rules/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/auto-height/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/auto-height/modal.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/basic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/draggable/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/draggable/modal.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/dynamic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/dynamic/modal.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/extra/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/extra/modal.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/shared-data/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-modal/shared-data/modal.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/basic/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/custom-cell/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/edit-cell/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/edit-row/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/fixed/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/form/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/mock-api.ts create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/remote/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/table-data.ts create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/tree/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/demos/vben-vxe-table/virtual/index.vue create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/build.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/concept.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/development.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/external-module.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/icons.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/route.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/server.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/settings.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/essentials/styles.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/access.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/check-updates.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/features.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/layout.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/loading.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/locale.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/login.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/theme.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/in-depth/ui-framework.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/introduction/changelog.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/introduction/quick-start.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/introduction/roadmap.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/introduction/thin.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/introduction/vben.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/introduction/why.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/other/faq.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/other/project-update.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/other/remove-code.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/changeset.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/cli.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/dir.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/standard.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/tailwindcss.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/test.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/guide/project/vite.md create mode 100644 Yi.Vben5.Vue3/docs/src/en/index.md create mode 100644 Yi.Vben5.Vue3/docs/src/friend-links/index.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/build.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/concept.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/development.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/external-module.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/icons.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/route.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/server.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/settings.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/essentials/styles.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/access.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/check-updates.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/features.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/layout.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/loading.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/locale.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/login.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/theme.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/in-depth/ui-framework.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/introduction/changelog.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/introduction/quick-start.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/introduction/roadmap.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/introduction/thin.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/introduction/vben.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/introduction/why.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/other/faq.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/other/project-update.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/other/remove-code.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/changeset.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/cli.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/dir.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/standard.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/tailwindcss.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/test.md create mode 100644 Yi.Vben5.Vue3/docs/src/guide/project/vite.md create mode 100644 Yi.Vben5.Vue3/docs/src/index.md create mode 100644 Yi.Vben5.Vue3/docs/src/sponsor/personal.md create mode 100644 Yi.Vben5.Vue3/docs/tailwind.config.mjs create mode 100644 Yi.Vben5.Vue3/docs/tsconfig.json create mode 100644 Yi.Vben5.Vue3/eslint.config.mjs create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/commitlint-config/index.mjs create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/commitlint-config/package.json create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/build.config.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/package.json create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/command.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/comments.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/disableds.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/ignores.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/import.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/index.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/javascript.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/jsdoc.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/jsonc.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/node.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/perfectionist.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/prettier.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/regexp.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/test.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/turbo.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/typescript.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/unicorn.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/configs/vue.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/custom-config.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/index.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/src/util.ts create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/eslint-config/tsconfig.json create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/prettier-config/index.mjs create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/prettier-config/package.json create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/stylelint-config/index.mjs create mode 100644 Yi.Vben5.Vue3/internal/lint-configs/stylelint-config/package.json create mode 100644 Yi.Vben5.Vue3/internal/node-utils/build.config.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/package.json create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/__tests__/hash.test.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/__tests__/path.test.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/constants.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/date.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/fs.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/git.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/hash.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/index.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/monorepo.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/path.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/prettier.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/src/spinner.ts create mode 100644 Yi.Vben5.Vue3/internal/node-utils/tsconfig.json create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/build.config.ts create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/package.json create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/src/index.ts create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/src/module.d.ts create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/src/plugins/entry.ts create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/src/postcss.config.ts create mode 100644 Yi.Vben5.Vue3/internal/tailwind-config/tsconfig.json create mode 100644 Yi.Vben5.Vue3/internal/tsconfig/base.json create mode 100644 Yi.Vben5.Vue3/internal/tsconfig/library.json create mode 100644 Yi.Vben5.Vue3/internal/tsconfig/node.json create mode 100644 Yi.Vben5.Vue3/internal/tsconfig/package.json create mode 100644 Yi.Vben5.Vue3/internal/tsconfig/web-app.json create mode 100644 Yi.Vben5.Vue3/internal/tsconfig/web.json create mode 100644 Yi.Vben5.Vue3/internal/vite-config/build.config.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/package.json create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/config/application.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/config/common.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/config/index.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/config/library.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/index.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/options.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/archiver.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/extra-app-config.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/importmap.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/index.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/inject-app-loading/README.md create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/inject-app-loading/default-loading.html create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/inject-app-loading/index.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/inject-metadata.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/license.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/nitro-mock.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/print.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/plugins/vxe-table.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/typing.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/src/utils/env.ts create mode 100644 Yi.Vben5.Vue3/internal/vite-config/tsconfig.json create mode 100644 Yi.Vben5.Vue3/lefthook.yml create mode 100644 Yi.Vben5.Vue3/package.json create mode 100644 Yi.Vben5.Vue3/playground/.env create mode 100644 Yi.Vben5.Vue3/playground/.env.analyze create mode 100644 Yi.Vben5.Vue3/playground/.env.development create mode 100644 Yi.Vben5.Vue3/playground/.env.production create mode 100644 Yi.Vben5.Vue3/playground/__tests__/e2e/auth-login.spec.ts create mode 100644 Yi.Vben5.Vue3/playground/__tests__/e2e/common/auth.ts create mode 100644 Yi.Vben5.Vue3/playground/index.html create mode 100644 Yi.Vben5.Vue3/playground/package.json create mode 100644 Yi.Vben5.Vue3/playground/playwright.config.ts create mode 100644 Yi.Vben5.Vue3/playground/postcss.config.mjs create mode 100644 Yi.Vben5.Vue3/playground/src/adapter/component/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/adapter/form.ts create mode 100644 Yi.Vben5.Vue3/playground/src/adapter/vxe-table.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/core/auth.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/core/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/core/menu.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/core/user.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/download.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/json-bigint.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/params.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/status.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/table.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/examples/upload.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/request.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/system/dept.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/system/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/system/menu.ts create mode 100644 Yi.Vben5.Vue3/playground/src/api/system/role.ts create mode 100644 Yi.Vben5.Vue3/playground/src/app.vue create mode 100644 Yi.Vben5.Vue3/playground/src/bootstrap.ts create mode 100644 Yi.Vben5.Vue3/playground/src/layouts/auth.vue create mode 100644 Yi.Vben5.Vue3/playground/src/layouts/basic.vue create mode 100644 Yi.Vben5.Vue3/playground/src/layouts/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/locales/README.md create mode 100644 Yi.Vben5.Vue3/playground/src/locales/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/en-US/demos.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/en-US/examples.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/en-US/page.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/en-US/system.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/zh-CN/demos.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/zh-CN/examples.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/zh-CN/page.json create mode 100644 Yi.Vben5.Vue3/playground/src/locales/langs/zh-CN/system.json create mode 100644 Yi.Vben5.Vue3/playground/src/main.ts create mode 100644 Yi.Vben5.Vue3/playground/src/preferences.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/access.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/guard.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/core.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/modules/dashboard.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/modules/demos.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/modules/examples.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/modules/system.ts create mode 100644 Yi.Vben5.Vue3/playground/src/router/routes/modules/vben.ts create mode 100644 Yi.Vben5.Vue3/playground/src/store/auth.ts create mode 100644 Yi.Vben5.Vue3/playground/src/store/index.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/README.md create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/about/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/authentication/code-login.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/authentication/forget-password.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/authentication/login.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/authentication/qrcode-login.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/authentication/register.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/fallback/coming-soon.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/fallback/forbidden.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/fallback/internal-error.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/fallback/not-found.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/_core/fallback/offline.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/analytics/analytics-trends.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/analytics/analytics-visits-data.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/analytics/analytics-visits-sales.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/analytics/analytics-visits-source.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/analytics/analytics-visits.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/analytics/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/dashboard/workspace/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/access/admin-visible.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/access/button-control.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/access/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/access/menu-visible-403.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/access/super-visible.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/access/user-visible.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/active-icon/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/badge/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/breadcrumb/lateral-detail.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/breadcrumb/lateral.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/breadcrumb/level-detail.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/clipboard/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/file-download/base64.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/file-download/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/full-screen/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/hide-menu-children/children.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/hide-menu-children/parent.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/icons/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/json-bigint/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/login-expired/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/menu-query/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/new-window/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/request-params-serializer/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/tabs/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/tabs/tab-detail.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/vue-query/concurrency-caching.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/vue-query/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/vue-query/infinite-queries.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/vue-query/paginated-queries.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/vue-query/query-retries.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/vue-query/typing.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/features/watermark/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/nested/menu-1.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/nested/menu-2-1.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/nested/menu-3-1.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/demos/nested/menu-3-2-1.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/button-group/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/captcha/point-selection-captcha.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/captcha/slider-captcha.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/captcha/slider-rotate-captcha.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/count-to/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/doc-button.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/auto-height-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/base-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/dynamic-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/form-drawer-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/in-content-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/drawer/shared-data-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/ellipsis/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/api.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/basic.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/custom-layout.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/custom.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/dynamic.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/merge.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/modules/two-fields.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/query.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/form/rules.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/json-viewer/data.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/json-viewer/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/layout/col-page.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/loading/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/auto-height-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/base-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/blur-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/drag-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/dynamic-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/form-modal-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/in-content-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/nested-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/modal/shared-data-demo.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/motion/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/resize/basic.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/tippy/index.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/basic.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/custom-cell.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/edit-cell.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/edit-row.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/fixed.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/form.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/remote.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/table-data.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/tree.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/examples/vxe-table/virtual.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/dept/data.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/dept/list.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/dept/modules/form.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/menu/data.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/menu/list.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/menu/modules/form.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/role/data.ts create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/role/list.vue create mode 100644 Yi.Vben5.Vue3/playground/src/views/system/role/modules/form.vue create mode 100644 Yi.Vben5.Vue3/playground/tailwind.config.mjs create mode 100644 Yi.Vben5.Vue3/playground/tsconfig.json create mode 100644 Yi.Vben5.Vue3/playground/tsconfig.node.json create mode 100644 Yi.Vben5.Vue3/playground/vite.config.mts create mode 100644 Yi.Vben5.Vue3/pnpm-workspace.yaml create mode 100644 Yi.Vben5.Vue3/resource/image-20260101175759249.png create mode 100644 Yi.Vben5.Vue3/resource/image-20260101175912025.png create mode 100644 Yi.Vben5.Vue3/resource/image-20260101180006771.png create mode 100644 Yi.Vben5.Vue3/scripts/clean.mjs create mode 100644 Yi.Vben5.Vue3/scripts/deploy/Dockerfile create mode 100644 Yi.Vben5.Vue3/scripts/deploy/build-local-docker-image.sh create mode 100644 Yi.Vben5.Vue3/scripts/deploy/nginx.conf create mode 100644 Yi.Vben5.Vue3/scripts/turbo-run/README.md create mode 100644 Yi.Vben5.Vue3/scripts/turbo-run/build.config.ts create mode 100644 Yi.Vben5.Vue3/scripts/turbo-run/package.json create mode 100644 Yi.Vben5.Vue3/scripts/turbo-run/src/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/turbo-run/src/run.ts create mode 100644 Yi.Vben5.Vue3/scripts/turbo-run/tsconfig.json create mode 100644 Yi.Vben5.Vue3/scripts/vsh/README.md create mode 100644 Yi.Vben5.Vue3/scripts/vsh/build.config.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/package.json create mode 100644 Yi.Vben5.Vue3/scripts/vsh/src/check-circular/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/src/check-dep/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/src/code-workspace/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/src/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/src/lint/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/src/publint/index.ts create mode 100644 Yi.Vben5.Vue3/scripts/vsh/tsconfig.json create mode 100644 Yi.Vben5.Vue3/scripts/菜单图标替换sql/update_icon.sql create mode 100644 Yi.Vben5.Vue3/stylelint.config.mjs create mode 100644 Yi.Vben5.Vue3/tea.yaml create mode 100644 Yi.Vben5.Vue3/turbo.json create mode 100644 Yi.Vben5.Vue3/vben-admin.code-workspace create mode 100644 Yi.Vben5.Vue3/vitest.config.ts create mode 100644 Yi.Vben5.Vue3/vitest.workspace.ts diff --git a/Yi.Vben5.Vue3/.browserslistrc b/Yi.Vben5.Vue3/.browserslistrc new file mode 100644 index 00000000..dc3bc09a --- /dev/null +++ b/Yi.Vben5.Vue3/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/Yi.Vben5.Vue3/.changeset/README.md b/Yi.Vben5.Vue3/.changeset/README.md new file mode 100644 index 00000000..5654e898 --- /dev/null +++ b/Yi.Vben5.Vue3/.changeset/README.md @@ -0,0 +1,5 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/Yi.Vben5.Vue3/.changeset/config.json b/Yi.Vben5.Vue3/.changeset/config.json new file mode 100644 index 00000000..f954fb4b --- /dev/null +++ b/Yi.Vben5.Vue3/.changeset/config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "vbenjs/vue-vben-admin" } + ], + "commit": false, + "fixed": [["@vben-core/*", "@vben/*"]], + "snapshot": { + "prereleaseTemplate": "{tag}-{datetime}" + }, + "privatePackages": { "version": true, "tag": true }, + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/Yi.Vben5.Vue3/.commitlintrc.js b/Yi.Vben5.Vue3/.commitlintrc.js new file mode 100644 index 00000000..02e33fa6 --- /dev/null +++ b/Yi.Vben5.Vue3/.commitlintrc.js @@ -0,0 +1 @@ +export { default } from '@vben/commitlint-config'; diff --git a/Yi.Vben5.Vue3/.dockerignore b/Yi.Vben5.Vue3/.dockerignore new file mode 100644 index 00000000..52b833a9 --- /dev/null +++ b/Yi.Vben5.Vue3/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.git +.gitignore +*.md +dist +.turbo +dist.zip diff --git a/Yi.Vben5.Vue3/.editorconfig b/Yi.Vben5.Vue3/.editorconfig new file mode 100644 index 00000000..179aec6f --- /dev/null +++ b/Yi.Vben5.Vue3/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=2 +max_line_length = 100 +trim_trailing_whitespace = true +quote_type = single + +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/Yi.Vben5.Vue3/.gitattributes b/Yi.Vben5.Vue3/.gitattributes new file mode 100644 index 00000000..d4e5bd3e --- /dev/null +++ b/Yi.Vben5.Vue3/.gitattributes @@ -0,0 +1,11 @@ +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings + +# Automatically normalize line endings (to LF) for all text-based files. +* text=auto eol=lf + +# Declare files that will always have CRLF line endings on checkout. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary \ No newline at end of file diff --git a/Yi.Vben5.Vue3/.gitconfig b/Yi.Vben5.Vue3/.gitconfig new file mode 100644 index 00000000..4b28a69c --- /dev/null +++ b/Yi.Vben5.Vue3/.gitconfig @@ -0,0 +1,2 @@ +[core] + ignorecase = false diff --git a/Yi.Vben5.Vue3/.gitignore b/Yi.Vben5.Vue3/.gitignore new file mode 100644 index 00000000..96ab4755 --- /dev/null +++ b/Yi.Vben5.Vue3/.gitignore @@ -0,0 +1,54 @@ +node_modules +.DS_Store +dist +dist-ssr +dist.zip +dist.tar +dist.war +.nitro +.output +*-dist.zip +*-dist.tar +*-dist.war +coverage +*.local +**/.vitepress/cache +.cache +.turbo +.temp +dev-dist +.stylelintcache +yarn.lock +package-lock.json +pnpm-lock.yaml +.VSCodeCounter +**/backend-mock/data + +# local env files +.env.local +.env.*.local +.eslintcache + +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +vite.config.mts.* +vite.config.mjs.* +vite.config.js.* +vite.config.ts.* + +# Editor directories and files +.idea +# .vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +# 排除自动生成的类型文件 +apps/web-antd/types/components.d.ts +.history diff --git a/Yi.Vben5.Vue3/.gitpod.yml b/Yi.Vben5.Vue3/.gitpod.yml new file mode 100644 index 00000000..5fda2cf7 --- /dev/null +++ b/Yi.Vben5.Vue3/.gitpod.yml @@ -0,0 +1,6 @@ +ports: + - port: 5555 + onOpen: open-preview +tasks: + - init: npm i -g corepack && pnpm install + command: pnpm run dev:play diff --git a/Yi.Vben5.Vue3/.node-version b/Yi.Vben5.Vue3/.node-version new file mode 100644 index 00000000..ee5c2446 --- /dev/null +++ b/Yi.Vben5.Vue3/.node-version @@ -0,0 +1 @@ +22.1.0 diff --git a/Yi.Vben5.Vue3/.npmrc b/Yi.Vben5.Vue3/.npmrc new file mode 100644 index 00000000..21147aff --- /dev/null +++ b/Yi.Vben5.Vue3/.npmrc @@ -0,0 +1,13 @@ +registry = "https://registry.npmmirror.com" +public-hoist-pattern[]=lefthook +public-hoist-pattern[]=eslint +public-hoist-pattern[]=prettier +public-hoist-pattern[]=prettier-plugin-tailwindcss +public-hoist-pattern[]=stylelint +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=@commitlint/* +public-hoist-pattern[]=czg + +strict-peer-dependencies=false +auto-install-peers=true +dedupe-peer-dependents=true diff --git a/Yi.Vben5.Vue3/.prettierignore b/Yi.Vben5.Vue3/.prettierignore new file mode 100644 index 00000000..d0b0ca13 --- /dev/null +++ b/Yi.Vben5.Vue3/.prettierignore @@ -0,0 +1,18 @@ +dist +dev-dist +.local +.output.js +node_modules +.nvmrc +coverage +CODEOWNERS +.nitro +.output + + +**/*.svg +**/*.sh + +public +.npmrc +*-lock.yaml diff --git a/Yi.Vben5.Vue3/.prettierrc.mjs b/Yi.Vben5.Vue3/.prettierrc.mjs new file mode 100644 index 00000000..3e25d2cf --- /dev/null +++ b/Yi.Vben5.Vue3/.prettierrc.mjs @@ -0,0 +1 @@ +export { default } from '@vben/prettier-config'; diff --git a/Yi.Vben5.Vue3/.stylelintignore b/Yi.Vben5.Vue3/.stylelintignore new file mode 100644 index 00000000..f4b2db2c --- /dev/null +++ b/Yi.Vben5.Vue3/.stylelintignore @@ -0,0 +1,4 @@ +dist +public +__tests__ +coverage diff --git a/Yi.Vben5.Vue3/CHANGELOG.md b/Yi.Vben5.Vue3/CHANGELOG.md new file mode 100644 index 00000000..cb359556 --- /dev/null +++ b/Yi.Vben5.Vue3/CHANGELOG.md @@ -0,0 +1,323 @@ +# 1.4.1 + +**FEATURES** + +- Tinymce添加在antd原生表单/useVbenForm下的校验样式 +- useVbenForm 增加 Cascader(级联选择器) 组件 + +**BUG FIX** + +- 菜单管理 路由地址的必填项不生效 +- withDefaultPlaceholder中placeholder 在keepalive & 语言切换 & tab切换 显示不变的问题 + +**REFACTOR** + +- 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理 +- 代码生成 字典下拉加载 改为每次进入编辑页面都加载 +- ~~个人中心 账号绑定 样式/逻辑重构~~(回滚了 既要又要的问题) +- ~~个人中心 下拉卡片 昵称超长省略显示~~(回滚了 既要又要的问题) + +# 1.4.0 + +**FEATURES** + +- 菜单管理(通用方法) 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化) +- +- 菜单管理 级联删除 删除菜单和children + +**REFACTOR** + +- 除个人中心外所有本地路由改为从后端返回(需要执行更新sql) +- 流程图预览改为logicflow预览而非图片 ...然后后端又更新了 又改成iframe了 +- 菜单管理 新增角色校验(与后端权限保持一致) 只有superadmin可进行增删改 + +# 1.3.6 + +**BUG FIX** + +- oss配置switch切换 导致报错`存储类型找不到` +- 文件上传无法正确清除(innerList) + +# 1.3.5 + +**BUG FIX** + +- 某些带Vxe表格弹窗 关闭后没有正常清理表格数据的问题 + +# 1.3.4 + +**BUG FIX** + +- 文件上传多次触发导致数据不一致 https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC3BK6 + +**PREFORMANCE** + +- 浏览器返回按钮/手势操作时 弹窗不会被关闭(keepAlive导致) + +# 1.3.3 + +**BUG FIX** + +- 工作流list展示在开启缩放会有误差导致触底逻辑不会触发 + +**OTHER** + +- 代码生成预览对模板的提示...(下载都懒得点一下吗) + +# 1.3.2 + +**REFACTOR** + +- 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况) +- 菜单图标更新了一部分 sql同步更新 + +**OTHER** + +- 暂时锁死vite依赖 i18n会报错 + +# 1.3.1 + +**REFACTOR** + +- 所有Modal/Drawer表单关闭前会进行表单数据对比来弹出提示框 +- 字典项颜色选择从`原生input type=color`改为`vue3-colorpicker`组件 +- 全局Header: ClientID 更改大小写 [spring的问题导致](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS) + +**BUG FIX** + +- getVxePopupContainer逻辑调整 解决表格固定高度展开不全的问题 + +**FEATURES** + +- 字典渲染支持loading(length为0情况) + +**OTHERS** + +- useForm的组件改为异步导入(官方更新) bootstrap.js体积从2M降到600K 首屏加载速度提升 + +# 1.3.0 + +注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用 + +- `component: 'ImageUploadOld'` +- `component: 'FileUploadOld'` + +代替 **建议替换为新版本** + +大致变动: + +- `accept string[] -> string` +- `resultField 已经移除 统一使用ossId` +- `maxNumber -> maxCount` + +具体参数查看: `apps/web-antd/src/components/upload/src/props.d.ts` + +不再推荐使用useDescription, 这个版本会标记为@deprecated, 下个次版本将会移除 + +框架所有使用useDescription组件的会替换为原生(TODO) + +**REFACTOR** + +- **文件上传/图片上传重构(破坏性更新 不兼容之前的api)** +- **文件上传/图片上传不再支持url用法 强制使用ossId** +- TableSwitch组件重构 +- 管理员租户切换不再返回首页 直接刷新当前页(除特殊页面外会回到首页) +- 租户切换Select增加loading +- ~~modalLoading/drawerLoading改为调用内部的lock/unlock方法~~ 有待商榷暂时按老版本逻辑不变 +- 登录验证码 增加loading +- DictEnum使用const代替enum +- TinyMCE组件重构 移除冗余代码/功能 增加loading + +**ALPHA功能** + +- 弹窗表单数据更改关闭时的提示框(可能最终不会加入) 测试页面: 参数管理 + +**BUG FIX** + +- 重新登录 字典会unknown的情况[详细分析](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IBY27D) +- 测试菜单 请假申请 选中删除 需要根据状态判断 +- 修复文件/图片在Safari中无法上传 file-type库与Safari不兼容导致 +- 头像裁剪 图片加载失败一直处于loading无法上传 +- 头像裁剪 私有桶会拼接timestamp参数导致sign计算异常无法上传 感谢cropperjs作者 https://github.com/fengyuanchen/cropperjs/issues/1230 +- 租户选择下拉框会跟随body滚动(将下拉框样式的默认absolute改为fixed) + +**OTHER** + +- 字典管理 字典类型 表格选中行增加bold效果 +- 全局圆角修改 与antd保持一致 +- vditor(Markdown)升级3.10.9 +- 老版本的文件/图片上传将于下个版本移除 +- useDescription将于下个版本移除 +- getVxePopupContainer与新版Vxe不兼容 先返回body(会导致滚动不跟随)后续版本再优化 + +# 1.2.3 + +**BUG FIX** + +- `withDefaultPlaceholder`中将`placeholder`修改为computed, 解决后续使用`updateSchema`无法正常更新显示placeholder(响应式问题) + +- 流程定义 修改accept类型 解决无法拖拽上传 + +**FEATURES** + +- 增加`环境变量`打包配置demo -> build:antd:test +- 角色管理 勾选权限组件添加对错误用法的校验提示 + +**REFACTOR** + +- OAuth内部逻辑重构 增加新的默认OAuth登录方式 +- 重构部分setup组件为setup语法糖形式 + +# 1.2.2 + +**FEATURES** + +- 代码生成支持路径方式生成 +- 代码生成 支持选择表单生成类型(需要模板支持) +- 工作流 支持按钮权限 + +# 1.2.1 + +# BUG FIXES + +- 客户端管理 错误的status disabled +- modal/drawer升级后zIndex(2000)会遮挡Tinymce的下拉框zIndex(1300) + +# 1.2.0 + +**REFACTOR** + +- 菜单选择组件重构为Table形式 +- 字典相关功能重构 采用一个Map储存字典(之前为两个Map) +- 代码生成配置页面重构 去除步骤条 + +**Features** + +- 对接后端工作流 +- ~~通用的vxe-table排序事件(排序逻辑改为在排序事件中处理而非在api处理)~~ +- getDict/getDictOptions 提取公共逻辑 减少冗余代码 +- 字典新增对Number类型的支持 -> `getDictOptions('', true);`即可获取number类型的value +- 文件上传 增加上传进度条 下方上传提示 +- 图片上传 增加上传进度条 下方上传提示 +- oss下载进度提示 + +**BUG FIXES** + +- 字典项为空时getDict方法无限调用接口(无奈兼容 不给字典item本来就是错误用法) +- 表格排序翻页会丢失排序参数 +- 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况 +- requestClient缺失i18n内容 + +**OTHERS** + +- 用户管理 新增只获取一次(mounted)默认密码而非每次打开modal都获取 +- `apps/web-antd/src/utils/dict.ts` `getDict`方法将于下个版本删除 使用`getDictOptions`替代 +- VxeTable升级V4.10.0 +- 移除`@deprecated` `apps/web-antd/src/adapter/vxe-table.ts`的`tableCheckboxEvent`方法 +- 移除`由于更新方案弃用的` `apps/web-antd/src/adapter/vxe-table.ts`的`vxeSortEvent`方法 +- 移除apps下的ele和naive目录 + +# 1.1.3 + +**REFACTOR** + +- 重构: 判断vxe-table的复选框是否选中 + +**Bug Fixes** + +- 节点树在编辑 & 空数组(不勾选)情况 勾选节点会造成watch延迟触发 导致会带上父节点id造成id重复 +- 节点树在节点独立情况下的控制台warning: Invalid prop: type check failed for prop "value". Expected Array, got Object + +**Others** + +- 角色管理 优化Drawer布局 +- unplugin-vue-components插件(默认未开启) 需要排除Button组件 全局已经默认导入了 + +**BUG FIXES** + +- 操作日志详情 在description组件中json预览样式异常 +- 微服务版本 区间查询和中文搜索条件一起使用 无法正确查询 + +# 1.1.2 + +**Features** + +- Options转Enum工具函数 + +**OTHERS** + +- 菜单管理 改为虚拟滚动 +- 移除requestClient的一些冗余参数 +- 主动退出登录(右上角个人选项)不需要带跳转地址 + +**BUG FIXES** + +- 语言 漏加Content-Language请求头 +- 用户管理/岗位管理 左边部门树错误emit导致会调用两次列表api + +# 1.1.1 + +**REFACTOR** + +- 使用VxeTable重构OAuth账号绑定列表(替代antdv的Table) +- commonDownloadExcel方法 支持处理区间选择器字段导出excel + +**BUG FIXES** + +- 修复在Modal/Drawer中使用VxeTable时, 第二次打开表单参数依旧为第一次提交的参数 + +**OTHERS** + +- 废弃downloadExcel方法 统一使用commonDownloadExcel方法 + +# 1.1.0 + +**FEATURES** + +- 支持离线图标功能(全局可在内网环境中使用) + +**BUG FIXES** + +- 在VxeTable固定列时, getPopupContainer会导致宽度不够, 弹出层样式异常 解决办法(将弹窗元素挂载到VXe滚动容器上) + +**OTHERS** + +- 代码生成 - 字段信息修改 改为minWidth 防止在高分辨率屏幕出现空白 + +# 1.0.0 + +**FEATURES** + +- 用户管理 新增从参数取默认密码 +- 全局表格加上id 方便进行缓存列排序的操作 +- 支持菜单名称i18n +- 登录页 验证码登录 +- Markdown编辑/预览组件(基于vditor) +- VxeTable搜索表单 enter提交 + +**BUG FIXES** + +- 登录页面 关闭租户后下拉框没有正常隐藏 +- 字典管理 关闭租户不应显示`同步租户字典`按钮 +- 登录日志 漏掉了登录日志日期查询 +- 登出相关逻辑在并发(非await)情况下重复执行的问题 +- VxeTable在开启/关闭查询表单时 需要使用不同的padding +- VxeTable表格刷新 默认为reload 修改为在当前页刷新(query) +- 岗位管理 部门参数错误 +- 角色管理 菜单分配 节点独立下的回显及提交问题 +- 租户管理 套餐管理 回显时候`已选中节点`数量为0 +- 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息 + +**OTHERS** + +- 登录页 租户选择框浮层固定高度[256px] 超过高度自动滚动 +- 表单的Label默认方向改为`top` 支持\n换行 +- 所有表格的搜索加上allowClear属性 支持清除 +- vxe表格loading 只加载表格 不加载上面的表单 + +# 1.0.0-beta (2024-10-8) + +**FEATURES** + +- 基础功能已经开发完毕 +- 工作流相关模块等待后端重构后开发 diff --git a/Yi.Vben5.Vue3/LICENSE b/Yi.Vben5.Vue3/LICENSE new file mode 100644 index 00000000..6b0d5105 --- /dev/null +++ b/Yi.Vben5.Vue3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 dubai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Yi.Vben5.Vue3/README.md b/Yi.Vben5.Vue3/README.md new file mode 100644 index 00000000..4913f471 --- /dev/null +++ b/Yi.Vben5.Vue3/README.md @@ -0,0 +1,128 @@ +## **简介** + +基于 [ruoyi-plus-vben & vben5 & ant-design-vue ](https://gitee.com/dapppp/ruoyi-plus-vben.git) 的 前端项目 + +**完全兼容意框架[Yi.Admin](https://gitee.com/ccnetcore/Yi) rbac模块** + +| 组件/框架 | 版本 | +| :------------- | :----- | +| vben | 5.5.6 | +| ant-design-vue | 4.2.6 | +| vue | 3.5.13 | + +[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) + +## 提示 + +该仓库使用vben最新版本v5开发 + +v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd` + +**后端需要开启”furion格式的规范化api“**:路径在Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs + +## 预览 + +[预览地址点这里](https://yi.wjys.top/) + + +## 文档 + +[原ruoyi-plus-vben 框架文档](https://dapdap.top/) + +[Vben V5 文档地址](https://doc.vben.pro/) + +[后端 Yi 框架 文档地址](https://gitee.com/ccnetcore/Yi) + +## 🚀系统截图 + +![image-20260101175759249](/resource/image-20260101175759249.png) + +![image-20260101175912025](/resource/image-20260101175912025.png) + +![image-20260101180006771](/resource/image-20260101180006771.png) + + +## 安装使用 + +前置准备环境(只能用pnpm) + +```json +"packageManager": "pnpm", +"engines": { + "node": ">=20.15.0", + "pnpm": "latest" +}, +``` + +- 获取项目代码 + +```bash +git clone -b only-front --single-branch https://gitee.com/vichen2021/yiabp-mini.git +``` + +2. 安装依赖 + +```bash +cd yiabp-mini + +pnpm install +``` + +- 菜单图标替换 + +参考 [菜单图标替换](https://dapdap.top/guide/quick-start.html#%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87%E5%AF%BC%E5%85%A5) + + +2. **推荐** 使用菜单自行配置 (跟 cloud 版本打开方式一致) + +![图片](https://gitee.com/dapppp/ruoyi-plus-vben/raw/main/preview/菜单修改.png) + +使用内嵌 iframe 方式需要解决跨域问题 可参考[nginx.conf](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/script/docker/nginx/conf/nginx.conf#LC87)配置 + +- 运行 + +```bash +pnpm dev:antd +``` + +4. 打包 + +```bash +pnpm build:antd +``` + +## 这是一个特性 而不是一个bug! + +1. 菜单管理可分配 但只有`admin`/`superadmin`角色能访问 其他角色访问会到403页面 +2. 租户相关菜单可分配 但只有`superadmin`角色能访问 其他角色访问会到403页面 +3. 分配的租户管理员无法修改自己的角色的菜单(即管理员角色的菜单) 防止自己把自己权限弄没了 + +## Git 贡献提交规范 + +参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) + +- `feat` 增加新功能 +- `fix` 修复问题/BUG +- `style` 代码风格相关无影响运行结果的 +- `perf` 优化/性能提升 +- `refactor` 重构 +- `revert` 撤销修改 +- `test` 测试相关 +- `docs` 文档/注释 +- `chore` 依赖更新/脚手架配置修改等 +- `workflow` 工作流改进 +- `ci` 持续集成 +- `types` 类型定义文件更改 +- `wip` 开发中 + +## 浏览器支持 + +最低适配应该为`Chrome 88+`以上浏览器 详见 [css - where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility) + +本地开发推荐使用`Chrome` 最新版本浏览器 + +支持现代浏览器,不支持 IE + +| [ Edge](http://godban.github.io/browsers-support-badges/)
IE | [ Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | +| :-: | :-: | :-: | :-: | :-: | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | diff --git a/Yi.Vben5.Vue3/apps/backend-mock/.env b/Yi.Vben5.Vue3/apps/backend-mock/.env new file mode 100644 index 00000000..b20c4a65 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/.env @@ -0,0 +1,3 @@ +PORT=5320 +ACCESS_TOKEN_SECRET=access_token_secret +REFRESH_TOKEN_SECRET=refresh_token_secret diff --git a/Yi.Vben5.Vue3/apps/backend-mock/README.md b/Yi.Vben5.Vue3/apps/backend-mock/README.md new file mode 100644 index 00000000..401bda76 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/README.md @@ -0,0 +1,15 @@ +# @vben/backend-mock + +## Description + +Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。 + +## Running the app + +```bash +# development +$ pnpm run start + +# production mode +$ pnpm run build +``` diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts new file mode 100644 index 00000000..7ba01270 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts @@ -0,0 +1,14 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse } from '~/utils/response'; + +export default eventHandler((event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const codes = + MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? []; + + return useResponseSuccess(codes); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts new file mode 100644 index 00000000..df5737a2 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts @@ -0,0 +1,36 @@ +import { + clearRefreshTokenCookie, + setRefreshTokenCookie, +} from '~/utils/cookie-utils'; +import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils'; +import { forbiddenResponse } from '~/utils/response'; + +export default defineEventHandler(async (event) => { + const { password, username } = await readBody(event); + if (!password || !username) { + setResponseStatus(event, 400); + return useResponseError( + 'BadRequestException', + 'Username and password are required', + ); + } + + const findUser = MOCK_USERS.find( + (item) => item.username === username && item.password === password, + ); + + if (!findUser) { + clearRefreshTokenCookie(event); + return forbiddenResponse(event, 'Username or password is incorrect.'); + } + + const accessToken = generateAccessToken(findUser); + const refreshToken = generateRefreshToken(findUser); + + setRefreshTokenCookie(event, refreshToken); + + return useResponseSuccess({ + ...findUser, + accessToken, + }); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts new file mode 100644 index 00000000..ac6afe94 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts @@ -0,0 +1,15 @@ +import { + clearRefreshTokenCookie, + getRefreshTokenFromCookie, +} from '~/utils/cookie-utils'; + +export default defineEventHandler(async (event) => { + const refreshToken = getRefreshTokenFromCookie(event); + if (!refreshToken) { + return useResponseSuccess(''); + } + + clearRefreshTokenCookie(event); + + return useResponseSuccess(''); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts new file mode 100644 index 00000000..7df4d34f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts @@ -0,0 +1,33 @@ +import { + clearRefreshTokenCookie, + getRefreshTokenFromCookie, + setRefreshTokenCookie, +} from '~/utils/cookie-utils'; +import { verifyRefreshToken } from '~/utils/jwt-utils'; +import { forbiddenResponse } from '~/utils/response'; + +export default defineEventHandler(async (event) => { + const refreshToken = getRefreshTokenFromCookie(event); + if (!refreshToken) { + return forbiddenResponse(event); + } + + clearRefreshTokenCookie(event); + + const userinfo = verifyRefreshToken(refreshToken); + if (!userinfo) { + return forbiddenResponse(event); + } + + const findUser = MOCK_USERS.find( + (item) => item.username === userinfo.username, + ); + if (!findUser) { + return forbiddenResponse(event); + } + const accessToken = generateAccessToken(findUser); + + setRefreshTokenCookie(event, refreshToken); + + return accessToken; +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts new file mode 100644 index 00000000..880cc5ea --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts @@ -0,0 +1,28 @@ +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const data = ` + { + "code": 0, + "message": "success", + "data": [ + { + "id": 123456789012345678901234567890123456789012345678901234567890, + "name": "John Doe", + "age": 30, + "email": "john-doe@demo.com" + }, + { + "id": 987654321098765432109876543210987654321098765432109876543210, + "name": "Jane Smith", + "age": 25, + "email": "jane@demo.com" + } + ] + } + `; + setHeader(event, 'Content-Type', 'application/json'); + return data; +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts new file mode 100644 index 00000000..580cee4f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts @@ -0,0 +1,13 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse } from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const menus = + MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? []; + return useResponseSuccess(menus); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/status.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/status.ts new file mode 100644 index 00000000..41773e1d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/status.ts @@ -0,0 +1,5 @@ +export default eventHandler((event) => { + const { status } = getQuery(event); + setResponseStatus(event, Number(status)); + return useResponseError(`${status}`); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts new file mode 100644 index 00000000..c529ea1b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts @@ -0,0 +1,15 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { + sleep, + unAuthorizedResponse, + useResponseSuccess, +} from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + await sleep(600); + return useResponseSuccess(null); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].delete.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].delete.ts new file mode 100644 index 00000000..e48f051c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].delete.ts @@ -0,0 +1,15 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { + sleep, + unAuthorizedResponse, + useResponseSuccess, +} from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + await sleep(1000); + return useResponseSuccess(null); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts new file mode 100644 index 00000000..aa55c085 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts @@ -0,0 +1,15 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { + sleep, + unAuthorizedResponse, + useResponseSuccess, +} from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + await sleep(2000); + return useResponseSuccess(null); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts new file mode 100644 index 00000000..ae819b62 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts @@ -0,0 +1,61 @@ +import { faker } from '@faker-js/faker'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +const formatterCN = new Intl.DateTimeFormat('zh-CN', { + timeZone: 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +function generateMockDataList(count: number) { + const dataList = []; + + for (let i = 0; i < count; i++) { + const dataItem: Record = { + id: faker.string.uuid(), + pid: 0, + name: faker.commerce.department(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2021-01-01', to: '2022-12-31' }), + ), + remark: faker.lorem.sentence(), + }; + if (faker.datatype.boolean()) { + dataItem.children = Array.from( + { length: faker.number.int({ min: 1, max: 5 }) }, + () => ({ + id: faker.string.uuid(), + pid: dataItem.id, + name: faker.commerce.department(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2023-01-01', to: '2023-12-31' }), + ), + remark: faker.lorem.sentence(), + }), + ); + } + dataList.push(dataItem); + } + + return dataList; +} + +const mockData = generateMockDataList(10); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const listData = structuredClone(mockData); + + return useResponseSuccess(listData); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts new file mode 100644 index 00000000..5328b2fd --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts @@ -0,0 +1,12 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + return useResponseSuccess(MOCK_MENU_LIST); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/name-exists.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/name-exists.ts new file mode 100644 index 00000000..5599c22b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/name-exists.ts @@ -0,0 +1,28 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse } from '~/utils/response'; + +const namesMap: Record = {}; + +function getNames(menus: any[]) { + menus.forEach((menu) => { + namesMap[menu.name] = String(menu.id); + if (menu.children) { + getNames(menu.children); + } + }); +} +getNames(MOCK_MENU_LIST); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const { id, name } = getQuery(event); + + return (name as string) in namesMap && + (!id || namesMap[name as string] !== String(id)) + ? useResponseSuccess(true) + : useResponseSuccess(false); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/path-exists.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/path-exists.ts new file mode 100644 index 00000000..64774f79 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/path-exists.ts @@ -0,0 +1,28 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse } from '~/utils/response'; + +const pathMap: Record = { '/': 0 }; + +function getPaths(menus: any[]) { + menus.forEach((menu) => { + pathMap[menu.path] = String(menu.id); + if (menu.children) { + getPaths(menu.children); + } + }); +} +getPaths(MOCK_MENU_LIST); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const { id, path } = getQuery(event); + + return (path as string) in pathMap && + (!id || pathMap[path as string] !== String(id)) + ? useResponseSuccess(true) + : useResponseSuccess(false); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts new file mode 100644 index 00000000..2d6feae4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts @@ -0,0 +1,83 @@ +import { faker } from '@faker-js/faker'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; + +const formatterCN = new Intl.DateTimeFormat('zh-CN', { + timeZone: 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +const menuIds = getMenuIds(MOCK_MENU_LIST); + +function generateMockDataList(count: number) { + const dataList = []; + + for (let i = 0; i < count; i++) { + const dataItem: Record = { + id: faker.string.uuid(), + name: faker.commerce.product(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2022-01-01', to: '2025-01-01' }), + ), + permissions: faker.helpers.arrayElements(menuIds), + remark: faker.lorem.sentence(), + }; + + dataList.push(dataItem); + } + + return dataList; +} + +const mockData = generateMockDataList(100); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const { + page = 1, + MaxResultCount = 20, + name, + id, + remark, + startTime, + endTime, + status, + } = getQuery(event); + let listData = structuredClone(mockData); + if (name) { + listData = listData.filter((item) => + item.name.toLowerCase().includes(String(name).toLowerCase()), + ); + } + if (id) { + listData = listData.filter((item) => + item.id.toLowerCase().includes(String(id).toLowerCase()), + ); + } + if (remark) { + listData = listData.filter((item) => + item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()), + ); + } + if (startTime) { + listData = listData.filter((item) => item.createTime >= startTime); + } + if (endTime) { + listData = listData.filter((item) => item.createTime <= endTime); + } + if (['0', '1'].includes(status as string)) { + listData = listData.filter((item) => item.status === Number(status)); + } + return usePageResponseSuccess(page as string, MaxResultCount as string, listData); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts new file mode 100644 index 00000000..b360f8db --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts @@ -0,0 +1,73 @@ +import { faker } from '@faker-js/faker'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; + +function generateMockDataList(count: number) { + const dataList = []; + + for (let i = 0; i < count; i++) { + const dataItem = { + id: faker.string.uuid(), + imageUrl: faker.image.avatar(), + imageUrl2: faker.image.avatar(), + open: faker.datatype.boolean(), + status: faker.helpers.arrayElement(['success', 'error', 'warning']), + productName: faker.commerce.productName(), + price: faker.commerce.price(), + currency: faker.finance.currencyCode(), + quantity: faker.number.int({ min: 1, max: 100 }), + available: faker.datatype.boolean(), + category: faker.commerce.department(), + releaseDate: faker.date.past(), + rating: faker.number.float({ min: 1, max: 5 }), + description: faker.commerce.productDescription(), + weight: faker.number.float({ min: 0.1, max: 10 }), + color: faker.color.human(), + inProduction: faker.datatype.boolean(), + tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()), + }; + + dataList.push(dataItem); + } + + return dataList; +} + +const mockData = generateMockDataList(100); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + await sleep(600); + + const { page, MaxResultCount, sortBy, sortOrder } = getQuery(event); + const listData = structuredClone(mockData); + if (sortBy && Reflect.has(listData[0], sortBy as string)) { + listData.sort((a, b) => { + if (sortOrder === 'asc') { + if (sortBy === 'price') { + return ( + Number.parseFloat(a[sortBy as string]) - + Number.parseFloat(b[sortBy as string]) + ); + } else { + return a[sortBy as string] > b[sortBy as string] ? 1 : -1; + } + } else { + if (sortBy === 'price') { + return ( + Number.parseFloat(b[sortBy as string]) - + Number.parseFloat(a[sortBy as string]) + ); + } else { + return a[sortBy as string] < b[sortBy as string] ? 1 : -1; + } + } + }); + } + + return usePageResponseSuccess(page as string, MaxResultCount as string, listData); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts new file mode 100644 index 00000000..ca4a500b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts @@ -0,0 +1 @@ +export default defineEventHandler(() => 'Test get handler'); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts new file mode 100644 index 00000000..698cf211 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts @@ -0,0 +1 @@ +export default defineEventHandler(() => 'Test post handler'); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts new file mode 100644 index 00000000..1bb9e602 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts @@ -0,0 +1,13 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse } from '~/utils/response'; + +export default eventHandler((event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + return useResponseSuccess({ + url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', + }); + // return useResponseError("test") +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts b/Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts new file mode 100644 index 00000000..cfa2346c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts @@ -0,0 +1,10 @@ +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse } from '~/utils/response'; + +export default eventHandler((event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + return useResponseSuccess(userinfo); +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/error.ts b/Yi.Vben5.Vue3/apps/backend-mock/error.ts new file mode 100644 index 00000000..e20beac4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/error.ts @@ -0,0 +1,7 @@ +import type { NitroErrorHandler } from 'nitropack'; + +const errorHandler: NitroErrorHandler = function (error, event) { + event.node.res.end(`[Error Handler] ${error.stack}`); +}; + +export default errorHandler; diff --git a/Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts b/Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts new file mode 100644 index 00000000..b51f22d8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts @@ -0,0 +1,19 @@ +import { forbiddenResponse, sleep } from '~/utils/response'; + +export default defineEventHandler(async (event) => { + event.node.res.setHeader( + 'Access-Control-Allow-Origin', + event.headers.get('Origin') ?? '*', + ); + if (event.method === 'OPTIONS') { + event.node.res.statusCode = 204; + event.node.res.statusMessage = 'No Content.'; + return 'OK'; + } else if ( + ['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) && + event.path.startsWith('/api/') + ) { + await sleep(Math.floor(Math.random() * 2000)); + return forbiddenResponse(event, '演示环境,禁止修改'); + } +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts b/Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts new file mode 100644 index 00000000..c0fc13e2 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts @@ -0,0 +1,20 @@ +import errorHandler from './error'; + +process.env.COMPATIBILITY_DATE = new Date().toISOString(); +export default defineNitroConfig({ + devErrorHandler: errorHandler, + errorHandler: '~/error', + routeRules: { + '/api/**': { + cors: true, + headers: { + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Allow-Headers': + 'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', + 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Expose-Headers': '*', + }, + }, + }, +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/package.json b/Yi.Vben5.Vue3/apps/backend-mock/package.json new file mode 100644 index 00000000..cc0b8d53 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/package.json @@ -0,0 +1,21 @@ +{ + "name": "@vben/backend-mock", + "version": "0.0.1", + "description": "", + "private": true, + "license": "MIT", + "author": "", + "scripts": { + "build": "nitro build", + "start": "nitro dev" + }, + "dependencies": { + "@faker-js/faker": "catalog:", + "jsonwebtoken": "catalog:", + "nitropack": "catalog:" + }, + "devDependencies": { + "@types/jsonwebtoken": "catalog:", + "h3": "catalog:" + } +} diff --git a/Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts b/Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts new file mode 100644 index 00000000..99f544b6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts @@ -0,0 +1,13 @@ +export default defineEventHandler(() => { + return ` +

Hello Vben Admin

+

Mock service is starting

+ +`; +}); diff --git a/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json b/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json new file mode 100644 index 00000000..64f86c6b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json b/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json new file mode 100644 index 00000000..43008af1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.nitro/types/tsconfig.json" +} diff --git a/Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts b/Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts new file mode 100644 index 00000000..78f3aab7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts @@ -0,0 +1,26 @@ +import type { EventHandlerRequest, H3Event } from 'h3'; + +export function clearRefreshTokenCookie(event: H3Event) { + deleteCookie(event, 'jwt', { + httpOnly: true, + sameSite: 'none', + secure: true, + }); +} + +export function setRefreshTokenCookie( + event: H3Event, + refreshToken: string, +) { + setCookie(event, 'jwt', refreshToken, { + httpOnly: true, + maxAge: 24 * 60 * 60, // unit: seconds + sameSite: 'none', + secure: true, + }); +} + +export function getRefreshTokenFromCookie(event: H3Event) { + const refreshToken = getCookie(event, 'jwt'); + return refreshToken; +} diff --git a/Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts b/Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts new file mode 100644 index 00000000..8cfc6843 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts @@ -0,0 +1,59 @@ +import type { EventHandlerRequest, H3Event } from 'h3'; + +import jwt from 'jsonwebtoken'; + +import { UserInfo } from './mock-data'; + +// TODO: Replace with your own secret key +const ACCESS_TOKEN_SECRET = 'access_token_secret'; +const REFRESH_TOKEN_SECRET = 'refresh_token_secret'; + +export interface UserPayload extends UserInfo { + iat: number; + exp: number; +} + +export function generateAccessToken(user: UserInfo) { + return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' }); +} + +export function generateRefreshToken(user: UserInfo) { + return jwt.sign(user, REFRESH_TOKEN_SECRET, { + expiresIn: '30d', + }); +} + +export function verifyAccessToken( + event: H3Event, +): null | Omit { + const authHeader = getHeader(event, 'Authorization'); + if (!authHeader?.startsWith('Bearer')) { + return null; + } + + const token = authHeader.split(' ')[1]; + try { + const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload; + + const username = decoded.username; + const user = MOCK_USERS.find((item) => item.username === username); + const { password: _pwd, ...userinfo } = user; + return userinfo; + } catch { + return null; + } +} + +export function verifyRefreshToken( + token: string, +): null | Omit { + try { + const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload; + const username = decoded.username; + const user = MOCK_USERS.find((item) => item.username === username); + const { password: _pwd, ...userinfo } = user; + return userinfo; + } catch { + return null; + } +} diff --git a/Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts b/Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts new file mode 100644 index 00000000..192f30a0 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts @@ -0,0 +1,390 @@ +export interface UserInfo { + id: number; + password: string; + realName: string; + roles: string[]; + username: string; + homePath?: string; +} + +export const MOCK_USERS: UserInfo[] = [ + { + id: 0, + password: '123456', + realName: 'Vben', + roles: ['super'], + username: 'vben', + }, + { + id: 1, + password: '123456', + realName: 'Admin', + roles: ['admin'], + username: 'admin', + homePath: '/workspace', + }, + { + id: 2, + password: '123456', + realName: 'Jack', + roles: ['user'], + username: 'jack', + homePath: '/analytics', + }, +]; + +export const MOCK_CODES = [ + // super + { + codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'], + username: 'vben', + }, + { + // admin + codes: ['AC_100010', 'AC_100020', 'AC_100030'], + username: 'admin', + }, + { + // user + codes: ['AC_1000001', 'AC_1000002'], + username: 'jack', + }, +]; + +const dashboardMenus = [ + { + meta: { + order: -1, + title: 'page.dashboard.title', + }, + name: 'Dashboard', + path: '/dashboard', + redirect: '/analytics', + children: [ + { + name: 'Analytics', + path: '/analytics', + component: '/dashboard/analytics/index', + meta: { + affixTab: true, + title: 'page.dashboard.analytics', + }, + }, + { + name: 'Workspace', + path: '/workspace', + component: '/dashboard/workspace/index', + meta: { + title: 'page.dashboard.workspace', + }, + }, + ], + }, +]; + +const createDemosMenus = (role: 'admin' | 'super' | 'user') => { + const roleWithMenus = { + admin: { + component: '/demos/access/admin-visible', + meta: { + icon: 'mdi:button-cursor', + title: 'demos.access.adminVisible', + }, + name: 'AccessAdminVisibleDemo', + path: '/demos/access/admin-visible', + }, + super: { + component: '/demos/access/super-visible', + meta: { + icon: 'mdi:button-cursor', + title: 'demos.access.superVisible', + }, + name: 'AccessSuperVisibleDemo', + path: '/demos/access/super-visible', + }, + user: { + component: '/demos/access/user-visible', + meta: { + icon: 'mdi:button-cursor', + title: 'demos.access.userVisible', + }, + name: 'AccessUserVisibleDemo', + path: '/demos/access/user-visible', + }, + }; + + return [ + { + meta: { + icon: 'ic:baseline-view-in-ar', + keepAlive: true, + order: 1000, + title: 'demos.title', + }, + name: 'Demos', + path: '/demos', + redirect: '/demos/access', + children: [ + { + name: 'AccessDemos', + path: '/demosaccess', + meta: { + icon: 'mdi:cloud-key-outline', + title: 'demos.access.backendPermissions', + }, + redirect: '/demos/access/page-control', + children: [ + { + name: 'AccessPageControlDemo', + path: '/demos/access/page-control', + component: '/demos/access/index', + meta: { + icon: 'mdi:page-previous-outline', + title: 'demos.access.pageAccess', + }, + }, + { + name: 'AccessButtonControlDemo', + path: '/demos/access/button-control', + component: '/demos/access/button-control', + meta: { + icon: 'mdi:button-cursor', + title: 'demos.access.buttonControl', + }, + }, + { + name: 'AccessMenuVisible403Demo', + path: '/demos/access/menu-visible-403', + component: '/demos/access/menu-visible-403', + meta: { + authority: ['no-body'], + icon: 'mdi:button-cursor', + menuVisibleWithForbidden: true, + title: 'demos.access.menuVisible403', + }, + }, + roleWithMenus[role], + ], + }, + ], + }, + ]; +}; + +export const MOCK_MENUS = [ + { + menus: [...dashboardMenus, ...createDemosMenus('super')], + username: 'vben', + }, + { + menus: [...dashboardMenus, ...createDemosMenus('admin')], + username: 'admin', + }, + { + menus: [...dashboardMenus, ...createDemosMenus('user')], + username: 'jack', + }, +]; + +export const MOCK_MENU_LIST = [ + { + id: 1, + name: 'Workspace', + status: 1, + type: 'menu', + icon: 'mdi:dashboard', + path: '/workspace', + component: '/dashboard/workspace/index', + meta: { + icon: 'carbon:workspace', + title: 'page.dashboard.workspace', + affixTab: true, + order: 0, + }, + }, + { + id: 2, + meta: { + icon: 'carbon:settings', + order: 9997, + title: 'system.title', + badge: 'new', + badgeType: 'normal', + badgeVariants: 'primary', + }, + status: 1, + type: 'catalog', + name: 'System', + path: '/system', + children: [ + { + id: 201, + pid: 2, + path: '/system/menu', + name: 'SystemMenu', + authCode: 'System:Menu:List', + status: 1, + type: 'menu', + meta: { + icon: 'carbon:menu', + title: 'system.menu.title', + }, + component: '/system/menu/list', + children: [ + { + id: 20_101, + pid: 201, + name: 'SystemMenuCreate', + status: 1, + type: 'button', + authCode: 'System:Menu:Create', + meta: { title: 'common.create' }, + }, + { + id: 20_102, + pid: 201, + name: 'SystemMenuEdit', + status: 1, + type: 'button', + authCode: 'System:Menu:Edit', + meta: { title: 'common.edit' }, + }, + { + id: 20_103, + pid: 201, + name: 'SystemMenuDelete', + status: 1, + type: 'button', + authCode: 'System:Menu:Delete', + meta: { title: 'common.delete' }, + }, + ], + }, + { + id: 202, + pid: 2, + path: '/system/dept', + name: 'SystemDept', + status: 1, + type: 'menu', + authCode: 'System:Dept:List', + meta: { + icon: 'carbon:container-services', + title: 'system.dept.title', + }, + component: '/system/dept/list', + children: [ + { + id: 20_401, + pid: 201, + name: 'SystemDeptCreate', + status: 1, + type: 'button', + authCode: 'System:Dept:Create', + meta: { title: 'common.create' }, + }, + { + id: 20_402, + pid: 201, + name: 'SystemDeptEdit', + status: 1, + type: 'button', + authCode: 'System:Dept:Edit', + meta: { title: 'common.edit' }, + }, + { + id: 20_403, + pid: 201, + name: 'SystemDeptDelete', + status: 1, + type: 'button', + authCode: 'System:Dept:Delete', + meta: { title: 'common.delete' }, + }, + ], + }, + ], + }, + { + id: 9, + meta: { + badgeType: 'dot', + order: 9998, + title: 'demos.vben.title', + icon: 'carbon:data-center', + }, + name: 'Project', + path: '/vben-admin', + type: 'catalog', + status: 1, + children: [ + { + id: 901, + pid: 9, + name: 'VbenDocument', + path: '/vben-admin/document', + component: 'IFrameView', + type: 'embedded', + status: 1, + meta: { + icon: 'carbon:book', + iframeSrc: 'https://doc.vben.pro', + title: 'demos.vben.document', + }, + }, + { + id: 902, + pid: 9, + name: 'VbenGithub', + path: '/vben-admin/github', + component: 'IFrameView', + type: 'link', + status: 1, + meta: { + icon: 'carbon:logo-github', + link: 'https://github.com/vbenjs/vue-vben-admin', + title: 'Github', + }, + }, + { + id: 903, + pid: 9, + name: 'VbenAntdv', + path: '/vben-admin/antdv', + component: 'IFrameView', + type: 'link', + status: 0, + meta: { + icon: 'carbon:hexagon-vertical-solid', + badgeType: 'dot', + link: 'https://ant.vben.pro', + title: 'demos.vben.antdv', + }, + }, + ], + }, + { + id: 10, + component: '_core/about/index', + type: 'menu', + status: 1, + meta: { + icon: 'lucide:copyright', + order: 9999, + title: 'demos.vben.about', + }, + name: 'About', + path: '/about', + }, +]; + +export function getMenuIds(menus: any[]) { + const ids: number[] = []; + menus.forEach((item) => { + ids.push(item.id); + if (item.children && item.children.length > 0) { + ids.push(...getMenuIds(item.children)); + } + }); + return ids; +} diff --git a/Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts b/Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts new file mode 100644 index 00000000..53bb7ced --- /dev/null +++ b/Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts @@ -0,0 +1,68 @@ +import type { EventHandlerRequest, H3Event } from 'h3'; + +export function useResponseSuccess(data: T) { + return { + code: 0, + data, + error: null, + message: 'ok', + }; +} + +export function usePageResponseSuccess( + page: number | string, + MaxResultCount: number | string, + list: T[], + { message = 'ok' } = {}, +) { + const pageData = pagination( + Number.parseInt(`${page}`), + Number.parseInt(`${MaxResultCount}`), + list, + ); + + return { + ...useResponseSuccess({ + items: pageData, + total: list.length, + }), + message, + }; +} + +export function useResponseError(message: string, error: any = null) { + return { + code: -1, + data: null, + error, + message, + }; +} + +export function forbiddenResponse( + event: H3Event, + message = 'Forbidden Exception', +) { + setResponseStatus(event, 403); + return useResponseError(message, message); +} + +export function unAuthorizedResponse(event: H3Event) { + setResponseStatus(event, 401); + return useResponseError('Unauthorized Exception', 'Unauthorized Exception'); +} + +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function pagination( + pageNo: number, + MaxResultCount: number, + array: T[], +): T[] { + const offset = (pageNo - 1) * Number(MaxResultCount); + return offset + Number(MaxResultCount) >= array.length + ? array.slice(offset) + : array.slice(offset, offset + Number(MaxResultCount)); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/.env b/Yi.Vben5.Vue3/apps/web-antd/.env new file mode 100644 index 00000000..1850fc64 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/.env @@ -0,0 +1,8 @@ +# 应用标题 +VITE_APP_TITLE=Yi Admin + +# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 +VITE_APP_NAMESPACE=vben-web-antd + +# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 +VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key diff --git a/Yi.Vben5.Vue3/apps/web-antd/.env.analyze b/Yi.Vben5.Vue3/apps/web-antd/.env.analyze new file mode 100644 index 00000000..dacf2fed --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/.env.analyze @@ -0,0 +1,7 @@ +# public path +VITE_BASE=/ + +# Basic interface address SPA +VITE_GLOB_API_URL=/api/app + +VITE_VISUALIZER=true diff --git a/Yi.Vben5.Vue3/apps/web-antd/.env.development b/Yi.Vben5.Vue3/apps/web-antd/.env.development new file mode 100644 index 00000000..435f9410 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/.env.development @@ -0,0 +1,25 @@ +# 端口号 +VITE_PORT=5666 + +VITE_BASE=/ +# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 +VITE_NITRO_MOCK=false +# 是否打开 devtools,true 为打开,false 为关闭 +VITE_DEVTOOLS=false +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 后台请求路径 具体在vite.config.mts配置代理 +# VITE_GLOB_API_URL=http://101.37.70.137:19001/api/app +VITE_GLOB_API_URL=http://192.168.1.101:19001/api/app + +# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) +VITE_GLOB_ENABLE_ENCRYPT=false +# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 +VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== +# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 +VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= +# 客户端id +VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e + +VITE_GLOB_DEMO_MODE=false \ No newline at end of file diff --git a/Yi.Vben5.Vue3/apps/web-antd/.env.production b/Yi.Vben5.Vue3/apps/web-antd/.env.production new file mode 100644 index 00000000..40f2fd1c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/.env.production @@ -0,0 +1,35 @@ +VITE_BASE=/ + +# 是否开启压缩,可以设置为 none, brotli, gzip +VITE_COMPRESS=gzip + +# 是否开启 PWA +VITE_PWA=false + +# vue-router 的模式 +VITE_ROUTER_HISTORY=history + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true + +# 后端接口地址 +# VITE_GLOB_API_URL=/prod-api +VITE_GLOB_API_URL=https://yiapi.wjys.top/api + +# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) +VITE_GLOB_ENABLE_ENCRYPT=false +# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 +VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== +# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 +VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= +# 客户端id +VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e + +# 开启SSE +VITE_GLOB_SSE_ENABLE=false + +VITE_GLOB_DEMO_MODE=true + diff --git a/Yi.Vben5.Vue3/apps/web-antd/.env.test b/Yi.Vben5.Vue3/apps/web-antd/.env.test new file mode 100644 index 00000000..f66a206f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/.env.test @@ -0,0 +1,35 @@ +# 该文件是为了给一个增加环境变量打包的例子 +# 对应在根目录package.json -> build:antd:test 命令 + +VITE_BASE=/ + +# 是否开启压缩,可以设置为 none, brotli, gzip +VITE_COMPRESS=gzip + +# 是否开启 PWA +VITE_PWA=false + +# vue-router 的模式 +VITE_ROUTER_HISTORY=history + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true + +# 后端接口地址 +VITE_GLOB_API_URL=/test-api + +# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) +VITE_GLOB_ENABLE_ENCRYPT=true +# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 +VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== +# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 +VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= +# 客户端id +VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e + +# 开启SSE +VITE_GLOB_SSE_ENABLE=true + diff --git a/Yi.Vben5.Vue3/apps/web-antd/index.html b/Yi.Vben5.Vue3/apps/web-antd/index.html new file mode 100644 index 00000000..33d34a9e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + <%= VITE_APP_TITLE %> + + + +
+ + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/package.json b/Yi.Vben5.Vue3/apps/web-antd/package.json new file mode 100644 index 00000000..e5e9941c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/package.json @@ -0,0 +1,64 @@ +{ + "name": "@vben/web-antd", + "version": "1.4.1", + "homepage": "https://vben.pro", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "apps/web-antd" + }, + "license": "MIT", + "author": { + "name": "vben", + "email": "ann.vben@gmail.com", + "url": "https://github.com/anncwb" + }, + "type": "module", + "scripts": { + "build": "pnpm vite build", + "build:analyze": "pnpm vite build --mode analyze", + "dev": "pnpm vite --mode development", + "preview": "vite preview", + "typecheck": "vue-tsc --noEmit --skipLibCheck" + }, + "imports": { + "#/*": "./src/*" + }, + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@tinymce/tinymce-vue": "^6.0.1", + "@vben/access": "workspace:*", + "@vben/common-ui": "workspace:*", + "@vben/constants": "workspace:*", + "@vben/hooks": "workspace:*", + "@vben/icons": "workspace:*", + "@vben/layouts": "workspace:*", + "@vben/locales": "workspace:*", + "@vben/plugins": "workspace:*", + "@vben/preferences": "workspace:*", + "@vben/request": "workspace:*", + "@vben/stores": "workspace:*", + "@vben/styles": "workspace:*", + "@vben/types": "workspace:*", + "@vben/utils": "workspace:*", + "@vueuse/core": "catalog:", + "ant-design-vue": "catalog:", + "cropperjs": "^1.6.2", + "crypto-js": "^4.2.0", + "dayjs": "catalog:", + "echarts": "^5.5.1", + "jsencrypt": "^3.3.2", + "lodash-es": "^4.17.21", + "pinia": "catalog:", + "tinymce": "^7.3.0", + "unplugin-vue-components": "^0.27.3", + "vue": "catalog:", + "vue-router": "catalog:", + "vue3-colorpicker": "^2.3.0" + }, + "devDependencies": { + "@types/crypto-js": "^4.2.2", + "@types/lodash-es": "^4.17.12" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs b/Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs new file mode 100644 index 00000000..3d807045 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config/postcss'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts new file mode 100644 index 00000000..2b3c1522 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts @@ -0,0 +1,258 @@ +/** + * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 + * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, + */ + +import type { Component } from 'vue'; + +import type { BaseFormComponentType } from '@vben/common-ui'; +import type { Recordable } from '@vben/types'; + +import { + computed, + defineAsyncComponent, + defineComponent, + getCurrentInstance, + h, + ref, +} from 'vue'; + +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; +import { $t } from '@vben/locales'; + +import { notification } from 'ant-design-vue'; + +import { FileUploadOld, ImageUploadOld } from '#/components/upload-old'; + +const RichTextarea = defineAsyncComponent(() => + import('#/components/tinymce/index').then((res) => res.Tinymce), +); + +const FileUpload = defineAsyncComponent(() => + import('#/components/upload').then((res) => res.FileUpload), +); + +const ImageUpload = defineAsyncComponent(() => + import('#/components/upload').then((res) => res.ImageUpload), +); + +const AutoComplete = defineAsyncComponent( + () => import('ant-design-vue/es/auto-complete'), +); +const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); +const Cascader = defineAsyncComponent( + () => import('ant-design-vue/es/cascader'), +); +const Checkbox = defineAsyncComponent( + () => import('ant-design-vue/es/checkbox'), +); +const CheckboxGroup = defineAsyncComponent(() => + import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), +); +const DatePicker = defineAsyncComponent( + () => import('ant-design-vue/es/date-picker'), +); +const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); +const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); +const InputNumber = defineAsyncComponent( + () => import('ant-design-vue/es/input-number'), +); +const InputPassword = defineAsyncComponent(() => + import('ant-design-vue/es/input').then((res) => res.InputPassword), +); +const Mentions = defineAsyncComponent( + () => import('ant-design-vue/es/mentions'), +); +const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio')); +const RadioGroup = defineAsyncComponent(() => + import('ant-design-vue/es/radio').then((res) => res.RadioGroup), +); +const RangePicker = defineAsyncComponent(() => + import('ant-design-vue/es/date-picker').then((res) => res.RangePicker), +); +const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate')); +const Select = defineAsyncComponent(() => import('ant-design-vue/es/select')); +const Space = defineAsyncComponent(() => import('ant-design-vue/es/space')); +const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch')); +const Textarea = defineAsyncComponent(() => + import('ant-design-vue/es/input').then((res) => res.Textarea), +); +const TimePicker = defineAsyncComponent( + () => import('ant-design-vue/es/time-picker'), +); +const TreeSelect = defineAsyncComponent( + () => import('ant-design-vue/es/tree-select'), +); +const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); + +const withDefaultPlaceholder = ( + component: T, + type: 'input' | 'select', + componentProps: Recordable = {}, +) => { + return defineComponent({ + name: component.name, + inheritAttrs: false, + setup: (props: any, { attrs, expose, slots }) => { + // 改为placeholder 解决在keepalive & 语言切换 & tab切换 显示不变的问题 + const computedPlaceholder = computed( + () => + props?.placeholder || + attrs?.placeholder || + $t(`ui.placeholder.${type}`), + ); + + // 透传组件暴露的方法 + const innerRef = ref(); + const publicApi: Recordable = {}; + expose(publicApi); + const instance = getCurrentInstance(); + instance?.proxy?.$nextTick(() => { + for (const key in innerRef.value) { + if (typeof innerRef.value[key] === 'function') { + publicApi[key] = innerRef.value[key]; + } + } + }); + return () => + h( + component, + { + ...componentProps, + placeholder: computedPlaceholder.value, + ...props, + ...attrs, + ref: innerRef, + }, + slots, + ); + }, + }); +}; + +// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 +export type ComponentType = + | 'ApiSelect' + | 'ApiTreeSelect' + | 'AutoComplete' + | 'Cascader' + | 'Checkbox' + | 'CheckboxGroup' + | 'DatePicker' + | 'DefaultButton' + | 'Divider' + | 'FileUpload' + | 'FileUploadOld' + | 'IconPicker' + | 'ImageUpload' + | 'ImageUploadOld' + | 'Input' + | 'InputNumber' + | 'InputPassword' + | 'Mentions' + | 'PrimaryButton' + | 'Radio' + | 'RadioGroup' + | 'RangePicker' + | 'Rate' + | 'RichTextarea' + | 'Select' + | 'Space' + | 'Switch' + | 'Textarea' + | 'TimePicker' + | 'TreeSelect' + | 'Upload' + | BaseFormComponentType; + +async function initComponentAdapter() { + const components: Partial> = { + // 如果你的组件体积比较大,可以使用异步加载 + // Button: () => + // import('xxx').then((res) => res.Button), + ApiSelect: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiSelect', + }, + 'select', + { + component: Select, + loadingSlot: 'suffixIcon', + visibleEvent: 'onDropdownVisibleChange', + modelPropName: 'value', + }, + ), + ApiTreeSelect: withDefaultPlaceholder( + { + ...ApiComponent, + name: 'ApiTreeSelect', + }, + 'select', + { + component: TreeSelect, + fieldNames: { label: 'label', value: 'value', children: 'children' }, + loadingSlot: 'suffixIcon', + modelPropName: 'value', + optionsPropName: 'treeData', + visibleEvent: 'onVisibleChange', + }, + ), + AutoComplete, + Cascader: withDefaultPlaceholder(Cascader, 'select'), + Checkbox, + CheckboxGroup, + DatePicker, + // 自定义默认按钮 + DefaultButton: (props, { attrs, slots }) => { + return h(Button, { ...props, attrs, type: 'default' }, slots); + }, + Divider, + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { + iconSlot: 'addonAfter', + inputComponent: Input, + modelValueProp: 'value', + }), + Input: withDefaultPlaceholder(Input, 'input'), + InputNumber: withDefaultPlaceholder(InputNumber, 'input'), + InputPassword: withDefaultPlaceholder(InputPassword, 'input'), + Mentions: withDefaultPlaceholder(Mentions, 'input'), + // 自定义主要按钮 + PrimaryButton: (props, { attrs, slots }) => { + return h(Button, { ...props, attrs, type: 'primary' }, slots); + }, + Radio, + RadioGroup, + RangePicker, + Rate, + Select: withDefaultPlaceholder(Select, 'select'), + Space, + Switch, + Textarea: withDefaultPlaceholder(Textarea, 'input'), + TimePicker, + TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), + Upload, + ImageUpload, + FileUpload, + RichTextarea, + ImageUploadOld, + FileUploadOld, + }; + + // 将组件注册到全局共享状态中 + globalShareState.setComponents(components); + + // 定义全局共享状态中的消息提示 + globalShareState.defineMessage({ + // 复制成功消息提示 + copyPreferencesSuccess: (title, content) => { + notification.success({ + description: content, + message: title, + placement: 'bottomRight', + }); + }, + }); +} + +export { initComponentAdapter }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts b/Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts new file mode 100644 index 00000000..93b62621 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts @@ -0,0 +1,56 @@ +import type { + VbenFormSchema as FormSchema, + VbenFormProps, +} from '@vben/common-ui'; + +import type { ComponentType } from './component'; + +import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; +import { $t } from '@vben/locales'; + +import { isArray } from 'lodash-es'; + +async function initSetupVbenForm() { + setupVbenForm({ + config: { + // ant design vue组件库默认都是 v-model:value + baseModelPropName: 'value', + + // 一些组件是 v-model:checked 或者 v-model:fileList + modelPropNameMap: { + Checkbox: 'checked', + Radio: 'checked', + RichTextarea: 'modelValue', + Switch: 'checked', + Upload: 'fileList', + }, + }, + defineRules: { + // 输入项目必填国际化适配 + required: (value, _params, ctx) => { + if (value === undefined || value === null || value.length === 0) { + return $t('ui.formRules.required', [ctx.label]); + } + return true; + }, + // 选择项目必填国际化适配 + selectRequired: (value, _params, ctx) => { + if ( + [false, null, undefined].includes(value) || + (isArray(value) && value.length === 0) + ) { + return $t('ui.formRules.selectRequired', [ctx.label]); + } + return true; + }, + }, + }); +} + +const useVbenForm = useForm; + +export { initSetupVbenForm, useVbenForm, z }; + +export type VbenFormSchema = FormSchema; +export type { VbenFormProps }; +export type FormSchemaGetter = () => VbenFormSchema[]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts b/Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts new file mode 100644 index 00000000..31849aea --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts @@ -0,0 +1,137 @@ +import type { VxeGridPropTypes } from '@vben/plugins/vxe-table'; + +import { h } from 'vue'; + +import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; + +import { Button, Image } from 'ant-design-vue'; + +import { useVbenForm } from './form'; + +setupVbenVxeTable({ + configVxeTable: (vxeUI) => { + vxeUI.setConfig({ + grid: { + align: 'center', + border: false, + minHeight: 180, + formConfig: { + // 全局禁用vxe-table的表单配置,使用formOptions + enabled: false, + }, + proxyConfig: { + autoLoad: true, + response: { + result: 'items', + total: 'totalCount', + list: 'items', + }, + showActiveMsg: true, + showResponseMsg: false, + }, + // 溢出展示形式 + showOverflow: true, + pagerConfig: { + // 默认条数 + pageSize: 10, + // 分页可选条数 + pageSizes: [10, 20, 30, 40, 50], + }, + rowConfig: { + // 鼠标移入行显示 hover 样式 + isHover: true, + // 点击行高亮 + isCurrent: false, + }, + columnConfig: { + // 可拖拽列宽 + resizable: true, + }, + // 右上角工具栏 + toolbarConfig: { + // 自定义列 + custom: true, + customOptions: { + icon: 'vxe-icon-setting', + }, + // 最大化 + zoom: true, + // 刷新 + refresh: true, + refreshOptions: { + // 默认为reload 修改为在当前页刷新 + code: 'query', + }, + }, + // 圆角按钮 + round: true, + // 表格尺寸 + size: 'medium', + customConfig: { + // 表格右上角自定义列配置 是否保存到localStorage + // 必须存在id参数才能使用 + storage: false, + }, + }, + }); + + // 表格配置项可以用 cellRender: { name: 'CellImage' }, + vxeUI.renderer.add('CellImage', { + renderTableDefault(_renderOpts, params) { + const { column, row } = params; + return h(Image, { src: row[column.field] }); + }, + }); + + // 表格配置项可以用 cellRender: { name: 'CellLink' }, + vxeUI.renderer.add('CellLink', { + renderTableDefault(renderOpts) { + const { props } = renderOpts; + return h( + Button, + { size: 'small', type: 'link' }, + { default: () => props?.text }, + ); + }, + }); + + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 + // vxeUI.formats.add + }, + useVbenForm, +}); + +export { useVbenVxeGrid }; + +export type * from '@vben/plugins/vxe-table'; + +/** + * 判断vxe-table的复选框是否选中 + * @param tableApi api + * @returns boolean + */ +export function vxeCheckboxChecked( + tableApi: ReturnType[1], +) { + return tableApi?.grid?.getCheckboxRecords?.()?.length > 0; +} + +/** + * 通用的 排序参数添加到请求参数中 + * @param params 请求参数 + * @param sortList vxe-table的排序参数 + */ +export function addSortParams( + params: Record, + sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[], +) { + // 这里是排序取消 length为0 就不添加参数了 + if (sortList.length === 0) { + return; + } + // 支持单/多字段排序 + const orderByColumn = sortList.map((item) => item.field).join(','); + const isAsc = sortList.map((item) => item.order).join(','); + params.orderByColumn = orderByColumn; + params.isAsc = isAsc; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts new file mode 100644 index 00000000..22edf6a7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts @@ -0,0 +1,42 @@ +export type ID = number | string; +export type IDS = (number | string)[]; + +export interface BaseEntity { + createBy?: string; + createDept?: string; + createTime?: string; + updateBy?: string; + updateTime?: string; +} + +/** + * 分页信息 + * @param rows 结果集 + * @param total 总数 + */ +export interface PageResult { + items: T[]; + totalCount: number; +} + +/** + * 分页查询参数 + * + * 排序支持的用法如下: + * {isAsc:"asc",orderByColumn:"id"} order by id asc + * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc + * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc + * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc + * + * @param SkipCount 当前页 + * @param MaxResultCount 每页大小 + * @param orderByColumn 排序字段 + * @param isAsc 是否升序 + */ +export interface PageQuery { + isAsc?: string; + orderByColumn?: string; + SkipCount?: number; + MaxResultCount?: number; + [key: string]: any; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts new file mode 100644 index 00000000..76cd8ac5 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts @@ -0,0 +1,204 @@ +import type { GrantType } from '@vben/common-ui'; +import type { HttpResponse } from '@vben/request'; + +import { h } from 'vue'; + +import { useAppConfig } from '@vben/hooks'; + +import { Modal } from 'ant-design-vue'; + +import { requestClient } from '#/api/request'; + +const { clientId, sseEnable } = useAppConfig( + import.meta.env, + import.meta.env.PROD, +); + +export namespace AuthApi { + /** + * @description: 所有登录类型都需要用到的 + * @param clientId 客户端ID 这里为必填项 但是在loginApi内部处理了 所以为可选 + * @param grantType 授权/登录类型 + * @param tenantId 租户id + */ + export interface BaseLoginParams { + clientId?: string; + grantType: GrantType; + tenantId: string; + } + + /** + * @description: oauth登录需要用到的参数 + * @param socialCode 第三方参数 + * @param socialState 第三方参数 + * @param source 与后端的 justauth.type.xxx的回调地址的source对应 + */ + export interface OAuthLoginParams extends BaseLoginParams { + socialCode: string; + socialState: string; + source: string; + } + + /** + * @description: 验证码登录需要用到的参数 + * @param code 验证码 可选(未开启验证码情况) + * @param uuid 验证码ID 可选(未开启验证码情况) + * @param username 用户名 + * @param password 密码 + */ + export interface SimpleLoginParams extends BaseLoginParams { + code?: string; + uuid?: string; + username: string; + password: string; + } + + export type LoginParams = OAuthLoginParams | SimpleLoginParams; + + // /** 登录接口参数 */ + // export interface LoginParams { + // code?: string; + // grantType: string; + // password: string; + // tenantId: string; + // username: string; + // uuid?: string; + // } + + /** 登录接口返回值 */ + export interface LoginResult { + access_token: string; + client_id: string; + expire_in: number; + } + + export interface RefreshTokenResult { + data: string; + status: number; + } +} + +/** + * 登录 + */ +export async function loginApi(data: AuthApi.LoginParams) { + return requestClient.post( + '/account/login', + { ...data, clientId }, + { + encrypt: true, + }, + ); +} + +/** + * 用户登出 + * @returns void + */ +export async function doLogout() { + const resp = await requestClient.post>( + 'account/logout', + null, + { + isTransformResponse: false, + }, + ); + // 无奈之举 对错误用法的提示 + if (resp.code === 401 && import.meta.env.DEV) { + Modal.destroyAll(); + Modal.warn({ + title: '后端配置出现错误', + centered: true, + content: h('div', { class: 'flex flex-col gap-2' }, [ + `检测到你的logout接口返回了401, 导致前端一直进入循环逻辑???`, + ...Array.from({ length: 3 }, () => + h( + 'span', + { class: 'font-bold text-red-500 text-[18px]' }, + '去检查你的后端配置!别盯着前端找问题了!这不是前端问题!', + ), + ), + ]), + }); + } +} + +/** + * 关闭sse连接 + * @returns void + */ +export function seeConnectionClose() { + /** + * 未开启sse 不需要处理 + */ + if (!sseEnable) { + return; + } + return requestClient.get('/resource/sse/close'); +} + +/** + * @param companyName 租户/公司名称 + * @param domain 绑定域名(不带http(s)://) 可选 + * @param tenantId 租户id + */ +export interface TenantOption { + companyName: string; + domain?: string; + tenantId: string; +} + +/** + * @param tenantEnabled 是否启用租户 + * @param voList 租户列表 + */ +export interface TenantResp { + tenantEnabled: boolean; + voList: TenantOption[]; +} + +/** + * 获取租户列表 下拉框使用 + */ +export function tenantList() { + return requestClient.get('/tenant/select'); +} + +/** + * vben的 先不删除 + * @returns string[] + */ +export async function getAccessCodesApi() { + return requestClient.get('/auth/codes'); +} + +/** + * 绑定第三方账号 + * @param source 绑定的来源 + * @returns 跳转url + */ +export function authBinding(source: string, tenantId: string) { + return requestClient.get(`/auth/binding/${source}`, { + params: { + domain: window.location.host, + tenantId, + }, + }); +} + +/** + * 取消绑定 + * @param id id + */ +export function authUnbinding(id: string) { + return requestClient.deleteWithMsg(`/auth/unlock/${id}`); +} + +/** + * oauth授权回调 + * @param data oauth授权 + * @returns void + */ +export function authCallback(data: AuthApi.OAuthLoginParams) { + return requestClient.post('/auth/social/callback', data); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts new file mode 100644 index 00000000..734ff637 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts @@ -0,0 +1,42 @@ +import { requestClient } from '#/api/request'; + +/** + * 发送短信验证码 + * @param phonenumber 手机号 + * @returns void + */ +export function sendSmsCode(phonenumber: string) { + return requestClient.get('/resource/sms/code', { + params: { phonenumber }, + }); +} + +/** + * 发送邮件验证码 + * @param email 邮箱 + * @returns void + */ +export function sendEmailCode(email: string) { + return requestClient.get('/resource/email/code', { + params: { email }, + }); +} + +/** + * @param img 图片验证码 需要和base64拼接 + * @param isEnableCaptcha 是否开启 + * @param uuid 验证码ID + */ +export interface CaptchaResponse { + isEnableCaptcha: boolean; + img: string; + uuid: string; +} + +/** + * 图片验证码 + * @returns resp + */ +export function captchaImage() { + return requestClient.get('/account/captcha-image'); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts new file mode 100644 index 00000000..04256867 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts @@ -0,0 +1,4 @@ +export * from './auth'; +export * from './menu'; +export * from './upload'; +export * from './user'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts new file mode 100644 index 00000000..64a71444 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts @@ -0,0 +1,45 @@ +import { requestClient } from '#/api/request'; + +/** + * @description: 菜单meta + * @param title 菜单名 + * @param icon 菜单图标 + * @param noCache 是否不缓存 + * @param link 外链链接 + */ +export interface MenuMeta { + icon: string; + link?: string; + noCache: boolean; + title: string; +} + +/** + * @description: 菜单 + * @param name 菜单名 + * @param path 菜单路径 + * @param hidden 是否隐藏 + * @param component 组件名称 Layout + * @param alwaysShow 总是显示 + * @param query 路由参数(json形式) + * @param meta 路由信息 + * @param children 子路由信息 + */ +export interface Menu { + alwaysShow?: boolean; + children: Menu[]; + component: string; + hidden: boolean; + meta: MenuMeta; + name: string; + path: string; + query?: string; + redirect?: string; +} + +/** + * 获取用户所有菜单 + */ +export async function getAllMenusApi() { + return requestClient.get('/account/Vue3Router/vben5'); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts new file mode 100644 index 00000000..f057dab5 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts @@ -0,0 +1,47 @@ +import type { AxiosRequestConfig } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +/** + * Axios上传进度事件 + */ +export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress']; + +/** + * 默认上传结果 + */ +export interface UploadResult { + url: string; + fileName: string; + ossId: string; +} + +/** + * 通过单文件上传接口 + * @param file 上传的文件 + * @param options 一些配置项 + * @param options.onUploadProgress 上传进度事件 + * @param options.signal 上传取消信号 + * @param options.otherData 其他请求参数 后端拓展可能会用到 + * @returns 上传结果 + */ +export function uploadApi( + file: Blob | File, + options?: { + onUploadProgress?: AxiosProgressEvent; + otherData?: Record; + signal?: AbortSignal; + }, +) { + const { onUploadProgress, signal, otherData = {} } = options ?? {}; + return requestClient.upload( + '/resource/oss/upload', + { file, ...otherData }, + { onUploadProgress, signal, timeout: 60_000 }, + ); +} + +/** + * 上传api type + */ +export type UploadApi = typeof uploadApi; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts new file mode 100644 index 00000000..e5cc1250 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts @@ -0,0 +1,47 @@ +import { requestClient } from '#/api/request'; + +export interface Role { + dataScope: string; + flag: boolean; + roleId: number; + roleKey: string; + roleName: string; + roleSort: number; + status: string; + superAdmin: boolean; +} + +export interface User { + avatar: string; + createTime: string; + deptId: number; + deptName: string; + email: string; + loginDate: string; + loginIp: string; + nickName: string; + phonenumber: string; + remark: string; + roles: Role[]; + sex: string; + status: string; + tenantId: string; + userId: number; + userName: string; + userType: string; +} + +export interface UserInfoResp { + permissionCodes: string[]; + roles: string[]; + roleCodes: string[]; + user: User; +} + +/** + * 获取用户信息 + * 存在返回null的情况(401) 不会抛出异常 需要手动抛异常 + */ +export async function getUserInfoApi() { + return requestClient.get('account'); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts new file mode 100644 index 00000000..2e32ef8e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts @@ -0,0 +1,28 @@ +import { requestClient } from './request'; + +/** + * @description: contentType + */ +export const ContentTypeEnum = { + // form-data upload + FORM_DATA: 'multipart/form-data;charset=UTF-8', + // form-data qs + FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8', + // json + JSON: 'application/json;charset=UTF-8', +} as const; + +/** + * 通用下载接口 封装一层 + * @param url 请求地址 + * @param data 请求参数 + * @returns blob二进制 + */ +export function commonExport(url: string, data: Record) { + return requestClient.post(url, data, { + data, + headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, + isTransformResponse: false, + responseType: 'blob', + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts new file mode 100644 index 00000000..4b0e0413 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts @@ -0,0 +1 @@ +export * from './core'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts new file mode 100644 index 00000000..dbb46e41 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts @@ -0,0 +1,90 @@ +import { requestClient } from '#/api/request'; + +export interface CommandStats { + name: string; + value: string; +} + +export interface RedisInfo { + [key: string]: string; +} + +export interface CacheInfo { + commandStats: CommandStats[]; + dbSize: number; + info: RedisInfo; +} + +export interface CacheName { + cacheName: string; + remark: string; +} + +export interface CacheValue { + cacheName: string; + cacheKey: string; + cacheValue: string; +} + +/** + * + * @returns redis信息 + */ +export function redisCacheInfo() { + return requestClient.get('/monitor/cache'); +} + +/** + * 查询缓存名称列表 + * @returns 缓存名称列表 + */ +export function listCacheName() { + return requestClient.get('/monitor-cache/name'); +} + +/** + * 查询缓存键名列表 + * @param cacheName 缓存名称 + * @returns 缓存键名列表 + */ +export function listCacheKey(cacheName: string) { + return requestClient.get(`/monitor-cache/key/${cacheName}`); +} + +/** + * 查询缓存内容 + * @param cacheName 缓存名称 + * @param cacheKey 缓存键名 + * @returns 缓存内容 + */ +export function getCacheValue(cacheName: string, cacheKey: string) { + return requestClient.get( + `/monitor-cache/value/${cacheName}/${cacheKey}`, + ); +} + +/** + * 清理指定名称缓存 + * @param cacheName 缓存名称 + */ +export function clearCacheName(cacheName: string) { + return requestClient.deleteWithMsg(`/monitor-cache/key/${cacheName}`); +} + +/** + * 清理指定键名缓存 + * @param cacheName 缓存名称 + * @param cacheKey 缓存键名 + */ +export function clearCacheKey(cacheName: string, cacheKey: string) { + return requestClient.deleteWithMsg( + `/monitor-cache/value/${cacheName}/${cacheKey}`, + ); +} + +/** + * 清理全部缓存 + */ +export function clearCacheAll() { + return requestClient.deleteWithMsg('/monitor-cache/clear'); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/index.ts new file mode 100644 index 00000000..bff7b7d3 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/index.ts @@ -0,0 +1,61 @@ +import type { LoginLog } from './model'; + +import type { IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + loginInfoClean = '/login-log/clean', + loginInfoExport = '/login-log/export', + root = '/login-log', + userUnlock = '/login-log/unlock', +} + +/** + * 登录日志列表 + * @param params 查询参数 + * @returns list[] + */ +export function loginInfoList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +/** + * 导出登录日志 + * @param data 表单参数 + * @returns excel + */ +export function loginInfoExport(data: any) { + return commonExport(Api.loginInfoExport, data); +} + +/** + * 移除登录日志 + * @param infoIds 登录日志id数组 + * @returns void + */ +export function loginInfoRemove(infoIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: infoIds.join(',') }, + }); +} + +/** + * 账号解锁 + * @param username 用户名(账号) + * @returns void + */ +export function userUnlock(username: string) { + return requestClient.get(`${Api.userUnlock}/${username}`, { + successMessageMode: 'message', + }); +} + +/** + * 清空全部登录日志 + * @returns void + */ +export function loginInfoClean() { + return requestClient.deleteWithMsg(Api.loginInfoClean); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts new file mode 100644 index 00000000..fea786aa --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts @@ -0,0 +1,11 @@ +export interface LoginLog { + id: string; + loginUser: string; + loginLocation: string; + loginIp: string; + browser: string; + os: string; + logMsg: string; + creationTime: string; + creatorId: string | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts new file mode 100644 index 00000000..3ba95269 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts @@ -0,0 +1,46 @@ +import type { OnlineUser } from './model'; + +import type { IDS, PageQuery, PageResult } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +enum Api { + root = '/online', +} + +/** + * 当前账号的在线设备 个人中心使用 + * @returns OnlineUser[] + */ +export function onlineDeviceList() { + return requestClient.get>(Api.root); +} + +/** + * 这里的分页参数无效 返回的是全部的分页 + * @param params 请求参数 + * @returns 结果 + */ +export function onlineList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +/** + * 强制下线 + * @param tokenId 连接Id + * @returns void + */ +export function forceLogout(tokenId: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: tokenId.join(',') }, + }); +} + +/** + * 个人中心用的 跟上面的不同是用的Post + * @param tokenId 连接Id + * @returns void + */ +export function forceLogout2(tokenId: string) { + return requestClient.deleteWithMsg(`${Api.root}/myself/${tokenId}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts new file mode 100644 index 00000000..846d5768 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts @@ -0,0 +1,10 @@ +export interface OnlineUser { + connnectionId?: string; + userId?: string; + userName?: string; + loginTime: number; + ipaddr?: string; + loginLocation?: string; + os?: string; + browser?: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts new file mode 100644 index 00000000..9b963776 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts @@ -0,0 +1,48 @@ +import type { OperationLog } from './model'; + +import type { IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + operLogClean = '/operation-log/clean', + operLogExport = '/operation-log/export', + root = '/operation-log', +} + +/** + * 操作日志分页 + * @param params 查询参数 + * @returns 分页结果 + */ +export function operLogList(params?: PageQuery) { + return requestClient.get>(Api.root, { + params, + }); +} + +/** + * 删除操作日志 + * @param operIds id/ids + */ +export function operLogRemove(operIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: operIds.join(',') }, + }); +} + +/** + * 清空全部分页日志 + */ +export function operLogClean() { + return requestClient.deleteWithMsg(Api.operLogClean); +} + +/** + * 导出操作日志 + * @param data 查询参数 + */ +export function operLogExport(data: Partial) { + return commonExport(Api.operLogExport, data); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts new file mode 100644 index 00000000..a5ea38ed --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts @@ -0,0 +1,14 @@ +export interface OperationLog { + id: string; + title: string; + operType: string; + requestMethod: string; + operUser: string; + operIp: string; + operLocation: string; + method: string; + requestParam: string; + requestResult: string; + creationTime: string; + creatorId: string | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts new file mode 100644 index 00000000..93700e02 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts @@ -0,0 +1,322 @@ +/** + * 该文件可自行根据业务逻辑进行调整 + */ + +import type { HttpResponse } from '@vben/request'; + +import { useAppConfig } from '@vben/hooks'; +import { $t } from '@vben/locales'; +import { preferences } from '@vben/preferences'; +import { + authenticateResponseInterceptor, + errorMessageResponseInterceptor, + RequestClient, + stringify, +} from '@vben/request'; +import { useAccessStore } from '@vben/stores'; + +import { message, Modal } from 'ant-design-vue'; + +import { useAuthStore } from '#/store'; +import { + decryptBase64, + decryptWithAes, + encryptBase64, + encryptWithAes, + generateAesKey, +} from '#/utils/encryption/crypto'; +import * as encryptUtil from '#/utils/encryption/jsencrypt'; + +const { apiURL, clientId, enableEncrypt, demoMode } = useAppConfig( + import.meta.env, + import.meta.env.PROD, +); + +/** + * 是否已经处在登出过程中了 一个标志位 + * 主要是防止一个页面会请求多个api 都401 会导致登出执行多次 + */ +let isLogoutProcessing = false; + +/** + * 定义一个401专用异常 用于可能会用到的区分场景? + */ +export class UnauthorizedException extends Error {} + +/** + * 演示模式错误,用于标识演示环境禁止修改的错误 + */ +export class DemoModeException extends Error { + constructor(message: string) { + super(message); + this.name = 'DemoModeException'; + // 添加标记,用于错误拦截器识别 + (this as any).__isDemoModeError = true; + } +} + +function createRequestClient(baseURL: string) { + const client = new RequestClient({ + // 后端地址 + baseURL, + // 消息提示类型 + errorMessageMode: 'message', + // 是否返回原生响应 比如:需要获取响应头时使用该属性 + isReturnNativeResponse: false, + // 需要对返回数据进行处理 + isTransformResponse: true, + }); + + /** + * 重新认证逻辑 + */ + async function doReAuthenticate() { + console.warn('Access token or refresh token is invalid or expired. '); + const accessStore = useAccessStore(); + const authStore = useAuthStore(); + accessStore.setAccessToken(null); + if ( + preferences.app.loginExpiredMode === 'modal' && + accessStore.isAccessChecked + ) { + accessStore.setLoginExpired(true); + } else { + await authStore.logout(); + } + } + + /** + * 刷新token逻辑 + */ + async function doRefreshToken() { + // 不需要 + // 保留此方法只是为了合并方便 + return ''; + } + + function formatToken(token: null | string) { + return token ? `Bearer ${token}` : null; + } + + client.addRequestInterceptor({ + fulfilled: (config) => { + // 演示模式:拦截所有修改操作 + if (demoMode) { + const method = config.method?.toUpperCase() || ''; + const isModifyMethod = ['DELETE', 'PATCH', 'POST', 'PUT'].includes( + method, + ); + // 排除登录等认证接口,允许通过 + const isAuthPath = + config.url?.includes('/auth/') || + config.url?.includes('/login') || + config.url?.includes('/logout'); + if (isModifyMethod && !isAuthPath) { + // 显示错误提示 + message.error('演示环境,禁止修改'); + // 抛出演示模式错误,错误拦截器会识别并跳过处理 + throw new DemoModeException('演示环境,禁止修改'); + } + } + + const accessStore = useAccessStore(); + // 添加token + config.headers.Authorization = formatToken(accessStore.accessToken); + /** + * locale跟后台不一致 需要转换 + */ + const language = preferences.app.locale.replace('-', '_'); + config.headers['Accept-Language'] = language; + config.headers['Content-Language'] = language; + /** + * 添加全局clientId + * 关于header的clientId被错误绑定到实体类 + * https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS + */ + config.headers.ClientID = clientId; + /** + * 格式化get/delete参数 + * 如果包含自定义的paramsSerializer则不走此逻辑 + */ + if ( + ['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') && + config.params && + !config.paramsSerializer + ) { + /** + * 1. 格式化参数 微服务在传递区间时间选择(后端的params Map类型参数)需要格式化key 否则接收不到 + * 2. 数组参数需要格式化 后端才能正常接收 会变成arr=1&arr=2&arr=3的格式来接收 + */ + config.paramsSerializer = (params) => + stringify(params, { arrayFormat: 'repeat' }); + } + + const { encrypt } = config; + // 全局开启请求加密功能 && 该请求开启 && 是post/put请求 + if ( + enableEncrypt && + encrypt && + ['POST', 'PUT'].includes(config.method?.toUpperCase() || '') + ) { + const aesKey = generateAesKey(); + config.headers['encrypt-key'] = encryptUtil.encrypt( + encryptBase64(aesKey), + ); + + config.data = + typeof config.data === 'object' + ? encryptWithAes(JSON.stringify(config.data), aesKey) + : encryptWithAes(config.data, aesKey); + } + return config; + }, + }); + + // 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里 + // 主要处理http状态码不为200(如网络异常/离线)的情况 必须放在在下面的响应拦截器之前 + const errorInterceptor = errorMessageResponseInterceptor( + (msg: string, error: any) => { + // 如果是演示模式错误,已经在请求拦截器中提示过了,这里不再提示 + if (error?.__isDemoModeError || error?.name === 'DemoModeException') { + return; + } + message.error(msg); + }, + ); + client.addResponseInterceptor(errorInterceptor); + + client.addResponseInterceptor({ + fulfilled: async (response) => { + const encryptKey = (response.headers ?? {})['encrypt-key']; + if (encryptKey) { + /** RSA私钥解密 拿到解密秘钥的base64 */ + const base64Str = encryptUtil.decrypt(encryptKey); + /** base64 解码 得到请求头的 AES 秘钥 */ + const aesSecret = decryptBase64(base64Str.toString()); + /** 使用aesKey解密 responseData */ + const decryptData = decryptWithAes( + response.data as unknown as string, + aesSecret, + ); + /** 赋值 需要转为对象 */ + response.data = JSON.parse(decryptData); + } + + const { isReturnNativeResponse, isTransformResponse } = response.config; + // 是否返回原生响应 比如:需要获取响应时使用该属性 + if (isReturnNativeResponse) { + return response; + } + // 不进行任何处理,直接返回 + // 用于页面代码可能需要直接获取code,data,message这些信息时开启 + if (!isTransformResponse) { + /** + * 需要判断下载二进制的情况 正常是返回二进制 报错会返回json + * 当type为blob且content-type为application/json时 则判断已经下载出错 + */ + if ( + response.config.responseType === 'blob' && + response.headers['content-type']?.includes?.('application/json') + ) { + // 这时候的data为blob类型 + const blob = response.data as unknown as Blob; + // 拿到字符串转json对象 + response.data = JSON.parse(await blob.text()); + // 然后按正常逻辑执行下面的代码(判断业务状态码) + } else { + // 其他情况 直接返回 + return response.data; + } + } + + const axiosResponseData = response.data; + if (!axiosResponseData) { + throw new Error($t('http.apiRequestFailed')); + } + + console.log('axiosResponseData', axiosResponseData); + // 适配新的后端数据结构: { statusCode, data, succeeded, errors, extras, timestamp } + const { statusCode, data, succeeded, errors, extras, timestamp } = + axiosResponseData; + + // 业务状态码为200且succeeded为true则请求成功 + const hasSuccess = statusCode === 200 && succeeded === true; + if (hasSuccess) { + const successMsg = $t(`http.operationSuccess`); + + if (response.config.successMessageMode === 'modal') { + Modal.success({ + content: successMsg, + title: $t('http.successTip'), + }); + } else if (response.config.successMessageMode === 'message') { + message.success(successMsg); + } + + // 直接返回data字段 + return data; + } + + // 在此处根据自己项目的实际情况对不同的statusCode执行不同的操作 + // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 + let timeoutMsg = ''; + switch (statusCode) { + case 401: { + // 已经在登出过程中 不再执行 + if (isLogoutProcessing) { + throw new UnauthorizedException(timeoutMsg); + } + isLogoutProcessing = true; + const _msg = $t('http.loginTimeout'); + const userStore = useAuthStore(); + userStore.logout().finally(() => { + message.error(_msg); + isLogoutProcessing = false; + }); + // 不再执行下面逻辑 + throw new UnauthorizedException(_msg); + } + default: { + // 优先使用errors字段作为错误信息 + if (errors && Array.isArray(errors) && errors.length > 0) { + timeoutMsg = errors.join(', '); + } else if (typeof errors === 'string') { + timeoutMsg = errors; + } else { + timeoutMsg = $t('http.apiRequestFailed'); + } + } + } + + // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 + // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 + if (response.config.errorMessageMode === 'modal') { + Modal.error({ + content: timeoutMsg, + title: $t('http.errorTip'), + }); + } else if (response.config.errorMessageMode === 'message') { + message.error(timeoutMsg); + } + + throw new Error(timeoutMsg || $t('http.apiRequestFailed')); + }, + }); + + // token过期的处理 + client.addResponseInterceptor( + authenticateResponseInterceptor({ + client, + doReAuthenticate, + doRefreshToken, + enableRefreshToken: preferences.app.enableRefreshToken, + formatToken, + }), + ); + + return client; +} + +export const requestClient = createRequestClient(apiURL); + +export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts new file mode 100644 index 00000000..1128c4c7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts @@ -0,0 +1,12 @@ +import type { ServerInfo } from './model'; + +import { requestClient } from '#/api/request'; + +/** + * 获取服务器信息 + * @returns 服务器信息 + */ +export function getServerInfo() { + return requestClient.get('/monitor-server/info'); +} + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts new file mode 100644 index 00000000..e4bd3179 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts @@ -0,0 +1,46 @@ +export interface CpuInfo { + coreTotal: number; + logicalProcessors: number; + cpuRate: number; +} + +export interface MemoryInfo { + totalRAM: string; + usedRam: string; + freeRam: string; + ramRate: number; +} + +export interface SystemInfo { + computerName: string; + osName: string; + serverIP: string; + osArch: string; +} + +export interface AppInfo { + name: string; + version: string; + startTime: string; + runTime: string; + rootPath: string; + webRootPath: string; +} + +export interface DiskInfo { + diskName: string; + typeName: string; + totalSize: string; + availableFreeSpace: string; + used: string; + availablePercent: number; +} + +export interface ServerInfo { + cpu: CpuInfo; + memory: MemoryInfo; + sys: SystemInfo; + app: AppInfo; + disk: DiskInfo[]; +} + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts new file mode 100644 index 00000000..55b6977a --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts @@ -0,0 +1,77 @@ +import type { Client } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + clientChangeStatus = '/client/changeStatus', + clientExport = '/client/export', + clientList = '/client/list', + root = '/client', +} + +/** + * 查询客户端分页列表 + * @param params 请求参数 + * @returns 列表 + */ +export function clientList(params?: PageQuery) { + return requestClient.get>(Api.clientList, { params }); +} + +/** + * 导出客户端excel + * @param data 请求参数 + */ +export function clientExport(data: Partial) { + return commonExport(Api.clientExport, data); +} + +/** + * 客户端详情 + * @param id id + * @returns 详情 + */ +export function clientInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} + +/** + * 客户端新增 + * @param data 参数 + */ +export function clientAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 客户端修改 + * @param data 参数 + */ +export function clientUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 客户端状态修改 + * @param data 状态 + */ +export function clientChangeStatus(data: any) { + const requestData = { + clientId: data.clientId, + status: data.status, + }; + return requestClient.putWithMsg(Api.clientChangeStatus, requestData); +} + +/** + * 客户端删除 + * @param ids id集合 + */ +export function clientRemove(ids: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: ids.join(',') }, + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts new file mode 100644 index 00000000..62af21fa --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts @@ -0,0 +1,12 @@ +export interface Client { + id: number; + clientId: string; + clientKey: string; + clientSecret: string; + grantTypeList: string[]; + grantType: string; + deviceType: string; + activeTimeout: number; + timeout: number; + status: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts new file mode 100644 index 00000000..1c0a9fa1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts @@ -0,0 +1,78 @@ +import type { SysConfig } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + configExport = '/config/export', + configInfoByKey = '/config/config-key', + configList = '/config/list', + configRefreshCache = '/config/refreshCache', + root = '/config', +} + +/** + * 系统参数分页列表 + * @param params 请求参数 + * @returns 列表 + */ +export function configList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +export function configInfo(configId: ID) { + return requestClient.get(`${Api.root}/${configId}`); +} + +/** + * 导出 + * @param data 参数 + */ +export function configExport(data: Partial) { + return commonExport(Api.configExport, data); +} + +/** + * 刷新缓存 + * @returns void + */ +export function configRefreshCache() { + return requestClient.deleteWithMsg(Api.configRefreshCache); +} + +/** + * 更新系统配置 + * @param data 参数 + */ +export function configUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 新增系统配置 + * @param data 参数 + */ +export function configAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 删除配置 + * @param configIds ids + */ +export function configRemove(configIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: configIds.join(',') }, + }); +} + +/** + * 获取配置信息 + * @param configKey configKey + * @returns value + */ +export function configInfoByKey(configKey: string) { + return requestClient.get(`${Api.configInfoByKey}/${configKey}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts new file mode 100644 index 00000000..3e8ecb73 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts @@ -0,0 +1,14 @@ +export interface SysConfig { + id: string; + configName: string; + configKey: string; + configValue: string; + configType: string | null; + orderNum: number; + remark: string | null; + isDeleted: boolean; + creationTime: string; + creatorId: string | null; + lastModifierId: string | null; + lastModificationTime: string | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts new file mode 100644 index 00000000..99ffdd8c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts @@ -0,0 +1,64 @@ +import type { Dept } from './model'; + +import type { ID } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +enum Api { + deptList = '/dept/list', + deptNodeInfo = '/dept/list/exclude', + root = '/dept', +} + +/** + * 部门列表 + * @returns list + */ +export function deptList(params?: { deptName?: string; status?: string }) { + return requestClient.get(Api.deptList, { params }); +} + +/** + * 查询部门列表(排除节点) + * @param deptId 部门ID + * @returns void + */ +export function deptNodeList(deptId: ID) { + return requestClient.get(`${Api.deptNodeInfo}/${deptId}`); +} + +/** + * 部门详情 + * @param deptId 部门id + * @returns 部门信息 + */ +export function deptInfo(deptId: ID) { + return requestClient.get(`${Api.root}/${deptId}`); +} + +/** + * 部门新增 + * @param data 参数 + */ +export function deptAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 部门更新 + * @param data 参数 + */ +export function deptUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 注意这里只允许单删除 + * @param deptId ID + * @returns void + */ +export function deptRemove(deptId: ID) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: deptId }, + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts new file mode 100644 index 00000000..9be4864b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts @@ -0,0 +1,14 @@ +export interface Dept { + creationTime: string; + creatorId?: string | null; + state: boolean; + deptName: string; + deptCode?: string; + leader?: string; + leaderName?: string; + parentId: string | null; + remark?: string; + orderNum: number; + id: string; + children?: Dept[]; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts new file mode 100644 index 00000000..b307e690 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts @@ -0,0 +1,17 @@ +export interface DictData { + id: string; + isDeleted: boolean; + orderNum: number; + state: boolean; + remark: string | null; + listClass: string | null; + cssClass: string | null; + dictType: string; + dictLabel: string | null; + dictValue: string; + isDefault: boolean; + creationTime: string; + creatorId: string | null; + lastModifierId: string | null; + lastModificationTime: string | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts new file mode 100644 index 00000000..a5d40593 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts @@ -0,0 +1,78 @@ +import type { DictData } from './dict-data-model'; + +import type { ID, IDS, PageQuery } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + dictDataExport = '/dict/data/export', + dictDataInfo = '/dictionary/dic-type', + dictDataList = '/dict/data/list', + root = '/dictionary', +} + +/** + * 主要是DictTag组件使用 + * @param dictType 字典类型 + * @returns 字典数据 + */ +export function dictDataInfo(dictType: string) { + return requestClient.get(`${Api.dictDataInfo}/${dictType}`); +} + +/** + * 字典数据 + * @param params 查询参数 + * @returns 字典数据列表 + */ +export function dictDataList(params?: PageQuery) { + return requestClient.get(Api.root, { params }); +} + +/** + * 导出字典数据 + * @param data 表单参数 + * @returns blob + */ +export function dictDataExport(data: Partial) { + return commonExport(Api.dictDataExport, data); +} + +/** + * 删除 + * @param dictIds 字典ID Array + * @returns void + */ +export function dictDataRemove(dictIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: dictIds.join(',') }, + }); +} + +/** + * 新增 + * @param data 表单参数 + * @returns void + */ +export function dictDataAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 修改 + * @param data 表单参数 + * @returns void + */ +export function dictDataUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 查询字典数据详细 + * @param id 字典ID + * @returns 字典数据 + */ +export function dictDetailInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts new file mode 100644 index 00000000..c82232fe --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts @@ -0,0 +1,13 @@ +export interface DictType { + id: string; + isDeleted: boolean; + orderNum: number; + state: boolean | null; + dictName: string; + dictType: string; + remark: string | null; + creationTime: string; + creatorId: string | null; + lastModifierId: string | null; + lastModificationTime: string | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts new file mode 100644 index 00000000..c7951469 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts @@ -0,0 +1,87 @@ +import type { DictType } from './dict-type-model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + dictOptionSelectList = '/dictionary-type/select-data-list', + dictTypeExport = '/dictionary-type/export', + dictTypeList = '/dictionary-type/list', + dictTypeRefreshCache = '/dictionary-type/refreshCache', + root = '/dictionary-type', +} + +/** + * 获取字典类型列表 + * @param params 请求参数 + * @returns list + */ +export function dictTypeList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +/** + * 导出字典类型列表 + * @param data 表单参数 + * @returns blob + */ +export function dictTypeExport(data: Partial) { + return commonExport(Api.dictTypeExport, data); +} + +/** + * 删除字典类型 + * @param dictIds 字典类型id数组 + * @returns void + */ +export function dictTypeRemove(dictIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: dictIds.join(',') }, + }); +} + +/** + * 刷新字典缓存 + * @returns void + */ +export function refreshDictTypeCache() { + return requestClient.deleteWithMsg(Api.dictTypeRefreshCache); +} + +/** + * 新增 + * @param data 表单参数 + * @returns void + */ +export function dictTypeAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 修改 + * @param data 表单参数 + * @returns void + */ +export function dictTypeUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 查询详情 + * @param dictId 字典类型id + * @returns 信息 + */ +export function dictTypeInfo(dictId: ID) { + return requestClient.get(`${Api.root}/${dictId}`); +} + +/** + * 这个在ele用到 v5用不上 + * 下拉框 返回值和list一样 + * @returns options + */ +export function dictOptionSelectList() { + return requestClient.get(Api.dictOptionSelectList); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts new file mode 100644 index 00000000..d264c08f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts @@ -0,0 +1,84 @@ +import type { Menu, MenuOption, MenuQuery, MenuResp } from './model'; + +import type { ID, IDS } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +enum Api { + menuList = '/menu/list', + menuTreeSelect = '/menu/tree', + root = '/menu', + tenantPackageMenuTreeselect = '/menu/tenantPackageMenuTreeselect', +} + +/** + * 菜单列表 + * @param params 参数 + * @returns 列表 + */ +export function menuList(params?: MenuQuery) { + return requestClient.get(Api.menuList, { params }); +} + +/** + * 菜单详情 + * @param menuId 菜单id + * @returns 菜单详情 + */ +export function menuInfo(menuId: ID) { + return requestClient.get(`${Api.root}/${menuId}`); +} + +/** + * 菜单新增 + * @param data 参数 + */ +export function menuAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 菜单更新 + * @param data 参数 + */ +export function menuUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 菜单删除 + * @param menuIds ids + */ +export function menuRemove(menuIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: menuIds.join(',') }, + }); +} + +/** + * 下拉框使用 返回所有的菜单 + * @returns [] + */ +export function menuTreeSelect() { + return requestClient.get(Api.menuTreeSelect); +} + +/** + * 租户套餐使用 + * @param packageId packageId + * @returns resp + */ +export function tenantPackageMenuTreeSelect(packageId: ID) { + return requestClient.get( + `${Api.tenantPackageMenuTreeselect}/${packageId}`, + ); +} + +/** + * 批量删除菜单 + * @param menuIds 菜单ids + * @returns void + */ +export function menuCascadeRemove(menuIds: IDS) { + return requestClient.deleteWithMsg(`${Api.root}/cascade/${menuIds}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/model.d.ts new file mode 100644 index 00000000..5ee550b7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/model.d.ts @@ -0,0 +1,57 @@ +export interface Menu { + id: string; + isDeleted: boolean; + creationTime: string; + creatorId: string | null; + lastModifierId: string | null; + lastModificationTime: string | null; + orderNum: number; + state: boolean; + menuName: string; + routerName?: string | null; + menuType: string; + permissionCode?: string | null; + parentId: string; + menuIcon?: string | null; + router?: string | null; + isLink: boolean; + isCache: boolean; + isShow: boolean; + remark?: string | null; + component?: string | null; + query?: string | null; + children?: Menu[]; +} + +/** + * @description 菜单信息 + * @param menuName 菜单名称 + */ +export interface MenuOption { + id: string; + parentId: string; + orderNum: number; + menuName: string; + menuType: string; + menuIcon?: string | null; + children?: MenuOption[] | null; +} + +/** + * @description 菜单返回 + * @param checkedKeys 选中的菜单id + * @param menus 菜单信息 + */ +export interface MenuResp { + checkedKeys: string[]; + menus: MenuOption[]; +} + +/** + * 菜单表单查询 + */ +export interface MenuQuery { + menuName?: string; + isShow?: boolean; + state?: boolean; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/index.ts new file mode 100644 index 00000000..0019e080 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/index.ts @@ -0,0 +1,53 @@ +import type { Notice } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +enum Api { + root = '/notice', +} + +/** + * 通知公告分页 + * @param params 分页参数 + * @returns 分页结果 + */ +export function noticeList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +/** + * 通知公告详情 + * @param id id + * @returns 详情 + */ +export function noticeInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} + +/** + * 通知公告新增 + * @param data 参数 + */ +export function noticeAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 通知公告更新 + * @param data 参数 + */ +export function noticeUpdate(data: any) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 通知公告删除 + * @param ids ids + */ +export function noticeRemove(ids: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: ids.join(',') }, + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/model.d.ts new file mode 100644 index 00000000..02c5773d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/model.d.ts @@ -0,0 +1,13 @@ +export interface Notice { + id: string; + title: string; + type: string; + content: string; + state: boolean; + isDeleted: boolean; + creationTime: string; + creatorId?: string | null; + lastModifierId?: string | null; + lastModificationTime?: string | null; + orderNum: number; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/index.ts new file mode 100644 index 00000000..e39b459b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/index.ts @@ -0,0 +1,48 @@ +import type { OssConfig } from './model'; + +import type { ID, IDS, PageQuery } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +enum Api { + ossConfigChangeStatus = '/resource/oss/config/changeStatus', + ossConfigList = '/resource/oss/config/list', + root = '/resource/oss/config', +} + +// 获取OSS配置列表 +export function ossConfigList(params?: PageQuery) { + return requestClient.get(Api.ossConfigList, { params }); +} + +// 获取OSS配置的信息 +export function ossConfigInfo(ossConfigId: ID) { + return requestClient.get(`${Api.root}/${ossConfigId}`); +} + +// 添加新的OSS配置 +export function ossConfigAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +// 更新现有的OSS配置 +export function ossConfigUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +// 删除OSS配置 +export function ossConfigRemove(ossConfigIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: ossConfigIds.join(',') }, + }); +} + +// 更改OSS配置的状态 +export function ossConfigChangeStatus(data: any) { + const requestData: Partial = { + ossConfigId: data.ossConfigId, + status: data.status, + configKey: data.configKey, + }; + return requestClient.putWithMsg(Api.ossConfigChangeStatus, requestData); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/model.d.ts new file mode 100644 index 00000000..b80b210d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/model.d.ts @@ -0,0 +1,16 @@ +export interface OssConfig { + ossConfigId: number; + configKey: string; + accessKey: string; + secretKey: string; + bucketName: string; + prefix: string; + endpoint: string; + domain: string; + isHttps: string; + region: string; + status: string; + ext1: string; + remark: string; + accessPolicy: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/index.ts new file mode 100644 index 00000000..ae38309b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/index.ts @@ -0,0 +1,77 @@ +import type { AxiosRequestConfig } from '@vben/request'; + +import type { OssFile } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { ContentTypeEnum } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + ossDownload = '/resource/oss/download', + ossInfo = '/resource/oss/listByIds', + ossList = '/resource/oss/list', + ossUpload = '/resource/oss/upload', + root = '/resource/oss', +} + +/** + * 文件list + * @param params 参数 + * @returns 分页 + */ +export function ossList(params?: PageQuery) { + return requestClient.get>(Api.ossList, { params }); +} + +/** + * 查询文件信息 返回为数组 + * @param ossIds id数组 + * @returns 信息数组 + */ +export function ossInfo(ossIds: ID | IDS) { + return requestClient.get(`${Api.ossInfo}/${ossIds}`); +} + +/** + * @deprecated 使用apps/web-antd/src/api/core/upload.ts uploadApi方法 + * @param file 文件 + * @returns void + */ +export function ossUpload(file: Blob | File) { + const formData = new FormData(); + formData.append('file', file); + return requestClient.postWithMsg(Api.ossUpload, formData, { + headers: { 'Content-Type': ContentTypeEnum.FORM_DATA }, + timeout: 30 * 1000, + }); +} + +/** + * 下载文件 返回为二进制 + * @param ossId ossId + * @param onDownloadProgress 下载进度(可选) + * @returns blob + */ +export function ossDownload( + ossId: ID, + onDownloadProgress?: AxiosRequestConfig['onDownloadProgress'], +) { + return requestClient.get(`${Api.ossDownload}/${ossId}`, { + responseType: 'blob', + timeout: 30 * 1000, + isTransformResponse: false, + onDownloadProgress, + }); +} + +/** + * 删除文件 + * @param ossIds id数组 + * @returns void + */ +export function ossRemove(ossIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: ossIds.join(',') }, + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/model.d.ts new file mode 100644 index 00000000..6eb4f371 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/model.d.ts @@ -0,0 +1,28 @@ +export interface OssFile { + ossId: string; + fileName: string; + originalName: string; + fileSuffix: string; + url: string; + createTime: string; + createBy: number; + createByName: string; + service: string; +} + +export interface OssConfig { + ossConfigId: number; + configKey: string; + accessKey: string; + secretKey: string; + bucketName: string; + prefix: string; + endpoint: string; + domain: string; + isHttps: string; + region: string; + status: string; + ext1: string; + remark: string; + accessPolicy: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/index.ts new file mode 100644 index 00000000..4722e8e6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/index.ts @@ -0,0 +1,77 @@ +import type { Post } from './model'; + +import type { ID, IDS, PageQuery } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + postExport = '/post/export', + postList = '/post/list', + postSelect = '/post/select-data-list', + root = '/post', +} + +/** + * 获取岗位列表 + * @param params 参数 + * @returns Post[] + */ +export function postList(params?: PageQuery) { + return requestClient.get(Api.root, { params }); +} + +/** + * 导出岗位信息 + * @param data 请求参数 + * @returns blob + */ +export function postExport(data: Partial) { + return commonExport(Api.postExport, data); +} + +/** + * 查询岗位信息 + * @param id 岗位id + * @returns 岗位信息 + */ +export function postInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} + +/** + * 岗位新增 + * @param data 参数 + * @returns void + */ +export function postAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 岗位更新 + * @param data 参数 + * @returns void + */ +export function postUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 岗位删除 + * @param postIds ids + * @returns void + */ +export function postRemove(postIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: postIds.join(',') }, + }); +} + +/** + * 获取岗位下拉列表 + * @returns 岗位 + */ +export function postOptionSelect(deptId: string) { + return requestClient.get(`${Api.postSelect}?keywords=${deptId}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/model.d.ts new file mode 100644 index 00000000..d0453669 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/model.d.ts @@ -0,0 +1,17 @@ +/** + * @description: Post interface + */ +export interface Post { + id: string; + isDeleted: boolean; + creationTime: string; + creatorId: string | null; + lastModifierId: string | null; + lastModificationTime: string | null; + orderNum: number; + state: boolean; + postCode: string; + postName: string; + deptId: string; + remark: string | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/index.ts new file mode 100644 index 00000000..6e68ef98 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/index.ts @@ -0,0 +1,65 @@ +import type { FileCallBack, UpdatePasswordParam, UserProfile } from './model'; + +import { buildUUID } from '@vben/utils'; + +import { requestClient } from '#/api/request'; + +enum Api { + root = '/user/profile', + updateAvatar = '/user/profile/avatar', + updatePassword = '/user/profile/updatePwd', +} + +/** + * 用户个人主页信息 + * @returns userInformation + */ +export function userProfile() { + return requestClient.get(Api.root); +} + +/** + * 更新用户个人主页信息 + * @param data + * @returns void + */ +export function userProfileUpdate(data: any) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 用户修改密码 (需要加密) + * @param data + * @returns void + */ +export function userUpdatePassword(data: UpdatePasswordParam) { + return requestClient.putWithMsg(Api.updatePassword, data, { + encrypt: true, + }); +} + +/** + * 用户更新个人头像 + * @param fileCallback data + * @returns void + */ +export function userUpdateAvatar(fileCallback: FileCallBack) { + /** 直接点击头像上传 filename为空 由于后台通过拓展名判断(默认文件名blob) 会上传失败 */ + let { file } = fileCallback; + const { filename } = fileCallback; + /** + * Blob转File类型 + * 1. 在直接点击确认 filename为空 取uuid作为文件名 + * 2. 选择上传必须转为File类型 Blob类型上传后台获取文件名为空 + */ + file = filename + ? new File([file], filename) + : new File([file], `${buildUUID()}.png`); + return requestClient.post( + Api.updateAvatar, + { + avatarfile: file, + }, + { headers: { 'Content-Type': 'multipart/form-data' } }, + ); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/model.d.ts new file mode 100644 index 00000000..bb1e4b38 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/profile/model.d.ts @@ -0,0 +1,75 @@ +export interface Dept { + deptId: number; + parentId: number; + parentName?: any; + ancestors: string; + deptName: string; + orderNum: number; + leader: string; + phone?: any; + email: string; + status: string; + createTime?: any; +} + +export interface Role { + roleId: number; + roleName: string; + roleKey: string; + roleSort: number; + dataScope: string; + menuCheckStrictly?: any; + deptCheckStrictly?: any; + status: string; + remark: string; + createTime?: any; + flag: boolean; + superAdmin: boolean; +} + +export interface User { + userId: number; + tenantId: string; + deptId: number; + userName: string; + nickName: string; + userType: string; + email: string; + phonenumber: string; + sex: string; + avatar: string; + status: string; + loginIp: string; + loginDate: string; + remark: string; + createTime: string; + dept: Dept; + roles: Role[]; + roleIds?: string[]; + postIds?: string[]; + roleId: number; + deptName: string; +} + +/** + * @description 用户个人主页信息 + * @param user 用户信息 + * @param roleGroup 角色名称 + * @param postGroup 岗位名称 + */ +export interface UserProfile { + user: User; + roleGroup: string; + postGroup: string; +} + +export interface UpdatePasswordParam { + oldPassword: string; + newPassword: string; +} + +interface FileCallBack { + name: string; + file: Blob; + filename: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/index.ts new file mode 100644 index 00000000..1d8c84eb --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/index.ts @@ -0,0 +1,161 @@ +import type { MenuResp } from '../menu/model'; +import type { User } from '../user/model'; +import type { DeptResp, Role } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + roleAuthUser = '/role/auth-user', + roleDataScope = '/role/data-scope', + roleDeptTree = '/role/dept-tree', + roleExport = '/role/export', + roleList = '/role/list', + roleMenuTree = '/role/menu-tree', + roleOptionSelect = '/role/select-data-list', + root = '/role', +} + +/** + * 查询角色分页列表 + * @param params 搜索条件 + * @returns 分页列表 + */ +export function roleList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +/** + * 导出角色信息 + * @param data 查询参数 + * @returns blob + */ +export function roleExport(data: Partial) { + return commonExport(Api.roleExport, data); +} + +/** + * 查询角色信息 + * @param roleId 角色id + * @returns 角色信息 + */ +export function roleInfo(roleId: ID) { + return requestClient.get(`${Api.root}/${roleId}`); +} + +/** + * 角色新增 + * @param data 参数 + * @returns void + */ +export function roleAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 角色更新 + * @param data 参数 + * @returns void + */ +export function roleUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 角色删除 + * @param roleIds ids + * @returns void + */ +export function roleRemove(roleIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: roleIds.join(',') }, + }); +} + +/** + * 更新数据权限 + * @param data + * @returns void + */ +export function roleDataScope(data: any) { + return requestClient.putWithMsg(Api.roleDataScope, data); +} + +/** + * @deprecated 全局并没有用到这个方法 + */ +export function roleOptionSelect(params?: any) { + return requestClient.get(Api.roleOptionSelect, { params }); +} + +/** + * 已分配角色的用户分页 + * @param params 请求参数 + * @returns 分页 + */ +export function roleAllocatedList(roleId: ID, params?: PageQuery) { + return requestClient.get>(`${Api.roleAuthUser}/${roleId}/true`, { params }); +} + +/** + * 未授权的用户 + * @param params + * @returns void + */ +export function roleUnallocatedList(roleId: ID, params?: PageQuery) { + return requestClient.get>(`${Api.roleAuthUser}/${roleId}/false`, { params }); +} + +/** + * 批量取消授权 + * @param roleId 角色ID + * @param userIds 用户ID集合 + * @returns void + */ +export function roleAuthCancelAll(roleId: ID, userIds: IDS) { + return requestClient.deleteWithMsg( + `${Api.roleAuthUser}`, + { + data: { + roleId, + userIds, + } + }, + ); +} + +/** + * 批量授权用户 + * @param roleId 角色ID + * @param userIds 用户ID集合 + * @returns void + */ +export function roleSelectAll(roleId: ID, userIds: IDS) { + return requestClient.postWithMsg( + `${Api.roleAuthUser}`, + { + roleId, + userIds, + }, + ); +} + +/** + * 根据角色id获取部门树 + * @param roleId 角色id + * @returns DeptResp + */ +export function roleDeptTree(roleId: ID) { + return requestClient.get(`${Api.roleDeptTree}/${roleId}`); +} + +/** + * 返回对应角色的菜单 + * @param roleId id + * @returns resp + */ +export function roleMenuTreeSelect(roleId: ID) { + return requestClient.get(`${Api.roleMenuTree}/${roleId}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/model.d.ts new file mode 100644 index 00000000..9758ea18 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/role/model.d.ts @@ -0,0 +1,30 @@ +export interface Role { + id: string; + creationTime: string; + creatorId?: string | null; + lastModifierId?: string | null; + lastModificationTime?: string | null; + isDeleted?: boolean; + orderNum: number; + state: boolean; + roleName: string; + roleCode: string; + remark?: string | null; + dataScope: string; + menuIds?: string[]; + deptIds?: string[]; +} + +export interface DeptOption { + id: string; + parentId: string | null; + orderNum: number; + deptName: string; + state: boolean; + children?: DeptOption[] | null; +} + +export interface DeptResp { + checkedKeys: string[]; + depts: DeptOption[]; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/index.ts new file mode 100644 index 00000000..668ade6c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/index.ts @@ -0,0 +1,25 @@ +import type { SocialInfo } from './model'; + +import type { ID } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +enum Api { + root = '/social', + socialList = '/social/list', +} + +/** + * 获取绑定的社交信息列表 + * @returns info + */ +export function socialList() { + return requestClient.get(Api.socialList); +} + +/** + * @deprecated 并没有用到这个方法 + */ +export function socialInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/model.d.ts new file mode 100644 index 00000000..1e3b0157 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/social/model.d.ts @@ -0,0 +1,26 @@ +export interface SocialInfo { + id: string; + userId: number; + tenantId: string; + authId: string; + source: string; + accessToken: string; + expireIn: number; + refreshToken: string; + openId: string; + userName: string; + nickName: string; + email: string; + avatar: string; + accessCode?: any; + unionId?: any; + scope: string; + tokenType: string; + idToken?: any; + macAlgorithm?: any; + macKey?: any; + code?: any; + oauthToken?: any; + oauthTokenSecret?: any; + createTime: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/index.ts new file mode 100644 index 00000000..642f4091 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/index.ts @@ -0,0 +1,93 @@ +import type { TenantPackage } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + packageChangeStatus = '/tenant/package/changeStatus', + packageExport = '/tenant/package/export', + packageList = '/tenant/package/list', + packageSelectList = '/tenant/package/selectList', + root = '/tenant/package', +} + +/** + * 租户套餐分页列表 + * @param params 请求参数 + * @returns 分页列表 + */ +export function packageList(params?: PageQuery) { + return requestClient.get>(Api.packageList, { + params, + }); +} + +/** + * 租户套餐下拉框 + * @returns 下拉框 + */ +export function packageSelectList() { + return requestClient.get(Api.packageSelectList); +} + +/** + * 租户套餐导出 + * @param data 参数 + * @returns blob + */ +export function packageExport(data: Partial) { + return commonExport(Api.packageExport, data); +} + +/** + * 租户套餐信息 + * @param id id + * @returns 信息 + */ +export function packageInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} + +/** + * 租户套餐新增 + * @param data data + * @returns void + */ +export function packageAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 租户套餐更新 + * @param data data + * @returns void + */ +export function packageUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 租户套餐状态变更 + * @param data data + * @returns void + */ +export function packageChangeStatus(data: Partial) { + const packageId = { + packageId: data.packageId, + status: data.status, + }; + return requestClient.putWithMsg(Api.packageChangeStatus, packageId); +} + +/** + * 租户套餐移除 + * @param ids ids + * @returns void + */ +export function packageRemove(ids: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: ids.join(',') }, + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/model.d.ts new file mode 100644 index 00000000..eb3af090 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant-package/model.d.ts @@ -0,0 +1,17 @@ +/** + * @description 租户套餐 + * @param packageId id + * @param packageName 名称 + * @param menuIds 菜单id 格式为[1,2,3] 返回为string 提交为数组 + * @param remark 备注 + * @param menuCheckStrictly 是否关联父节点 + * @param status 状态 + */ +export interface TenantPackage { + packageId: string; + packageName: string; + menuIds: number[] | string; + remark: string; + menuCheckStrictly: boolean; + status: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/index.ts new file mode 100644 index 00000000..d5cd88b8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/index.ts @@ -0,0 +1,113 @@ +import type { Tenant } from './model'; + +import type { ID, IDS, PageQuery } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + dictSync = '/tenant/syncTenantDict', + root = '/tenant', + tenantDynamic = '/tenant/dynamic', + tenantDynamicClear = '/tenant/dynamic/clear', + tenantExport = '/tenant/export', + tenantSyncPackage = '/tenant/syncTenantPackage', +} + +/** + * 查询租户分页列表 + * @param params 参数 + * @returns 分页 + */ +export function tenantList(params?: PageQuery) { + return requestClient.get(Api.root, { params }); +} + +/** + * 租户导出 + * @param data data + * @returns void + */ +export function tenantExport(data: Partial) { + return commonExport(Api.tenantExport, data); +} + +/** + * 查询租户信息 + * @param id id + * @returns 租户信息 + */ +export function tenantInfo(id: ID) { + return requestClient.get(`${Api.root}/${id}`); +} + +/** + * 新增租户 必须开启加密 + * @param data data + * @returns void + */ +export function tenantAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data, { encrypt: true }); +} + +/** + * 租户更新 + * @param data data + * @returns void + */ +export function tenantUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 租户删除 + * @param ids ids + * @returns void + */ +export function tenantRemove(ids: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: ids.join(',') }, + }); +} + +/** + * 动态切换租户 + * @param tenantId 租户ID + * @returns void + */ +export function tenantDynamicToggle(tenantId: string) { + return requestClient.get(`${Api.tenantDynamic}/${tenantId}`); +} + +/** + * 清除 动态切换租户 + * @returns void + */ +export function tenantDynamicClear() { + return requestClient.get(Api.tenantDynamicClear); +} + +/** + * 租户套餐同步 + * @param tenantId 租户id + * @param packageId 套餐id + * @returns void + */ +export function tenantSyncPackage(tenantId: string, packageId: string) { + return requestClient.get(Api.tenantSyncPackage, { + params: { packageId, tenantId }, + successMessageMode: 'message', + }); +} + +/** + * 同步租户字典 + * @param tenantId 租户ID + * @returns void + */ +export function dictSyncTenant(tenantId?: string) { + return requestClient.get(Api.dictSync, { + params: { tenantId }, + successMessageMode: 'message', + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/model.d.ts new file mode 100644 index 00000000..762a5911 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/tenant/model.d.ts @@ -0,0 +1,26 @@ +export interface Tenant { + id: string; + name: string; + entityVersion: number; + tenantConnectionString: string; + dbType: number; + isDeleted: boolean; + creationTime: string; + creatorId: string | null; + lastModifierId: string | null; + lastModificationTime: string | null; + // 以下字段可能来自 ExtraProperties 或后端扩展 + accountCount?: number; + address?: string; + companyName?: string; + contactPhone?: string; + contactUserName?: string; + domain?: string; + expireTime?: string; + intro?: string; + licenseNumber?: any; + packageId?: string; + remark?: string; + status?: string | boolean; + tenantId?: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/index.ts new file mode 100644 index 00000000..da5912df --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/index.ts @@ -0,0 +1,156 @@ +import type { + DeptTreeData, + ResetPwdParam, + User, + UserImportParam, +} from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { commonExport, ContentTypeEnum } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + deptTree = '/dept/tree', + listDeptUsers = '/user/dept', + root = '/user', + userAuthRole = '/user/authRole', + userExport = '/user/export', + userImport = '/user/importData', + userImportTemplate = '/user/importTemplate', + userResetPassword = '/account/rest-password', +} + +/** + * 获取用户列表 + * @param params + * @returns User + */ +export function userList(params?: PageQuery) { + return requestClient.get>(Api.root, { params }); +} + +/** + * 导出excel + * @param data data + * @returns blob + */ +export function userExport(data: Partial) { + return commonExport(Api.userExport, data); +} + +/** + * 从excel导入用户 + * @param data + * @returns void + */ +export function userImportData(data: UserImportParam) { + return requestClient.post<{ code: number; msg: string }>( + Api.userImport, + data, + { + headers: { + 'Content-Type': ContentTypeEnum.FORM_DATA, + }, + isTransformResponse: false, + }, + ); +} + +/** + * 下载用户导入模板 + * @returns blob + */ +export function downloadImportTemplate() { + return requestClient.post( + Api.userImportTemplate, + {}, + { + isTransformResponse: false, + responseType: 'blob', + }, + ); +} + +/** + * 可以不传ID 返回部门和角色options 需要获得原始数据 + * 不传ID时一定要带最后的/ + * @param userId 用户ID + * @returns 用户信息 + */ +export function findUserInfo(userId: ID) { + return requestClient.get(`${Api.root}/${userId}`); +} + +/** + * 新增用户 + * @param data data + * @returns void + */ +export function userAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 更新用户 + * @param data data + * @returns void + */ +export function userUpdate(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/${data.id}`, data); +} + +/** + * 删除用户 + * @param userIds 用户ID数组 + * @returns void + */ +export function userRemove(userIds: IDS) { + return requestClient.deleteWithMsg(Api.root, { + params: { ids: userIds.join(',') }, + }); +} + +/** + * 重置用户密码 需要加密 + * @param data + * @returns void + */ +export function userResetPassword(data: ResetPwdParam) { + return requestClient.putWithMsg(`${Api.userResetPassword}/${data.id}`, data, { + encrypt: true, + }); +} + +/** + * 这个方法未调用过 + * @param userId + * @returns void + */ +export function getUserAuthRole(userId: ID) { + return requestClient.get(`${Api.userAuthRole}/${userId}`); +} + +/** + * 这个方法未调用过 + * @param userId + * @returns void + */ +export function userAuthRoleUpdate(userId: ID, roleIds: number[]) { + return requestClient.putWithMsg(Api.userAuthRole, { roleIds, userId }); +} + +/** + * 获取部门树 + * @returns 部门树数组 + */ +export function getDeptTree() { + return requestClient.get(Api.deptTree); +} + +/** + * 获取部门下的所有用户信息 + */ +export function listUserByDeptId(deptId: ID) { + return requestClient.get(`${Api.listDeptUsers}/${deptId}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/model.d.ts new file mode 100644 index 00000000..435e317c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/system/user/model.d.ts @@ -0,0 +1,100 @@ +import type { Dept } from '../dept/model'; + +/** + * @description: 用户导入 + * @param updateSupport 是否覆盖数据 + * @param file excel文件 + */ +export interface UserImportParam { + updateSupport: boolean; + file: Blob | File; +} + +/** + * @description: 重置密码 + */ +export interface ResetPwdParam { + id: string; + password: string; +} + +export interface Role { + roleId: string; + roleName: string; + roleKey: string; + roleSort: number; + dataScope: string; + menuCheckStrictly?: boolean; + deptCheckStrictly?: boolean; + status: string; + remark: string; + createTime?: string; + flag: boolean; + superAdmin: boolean; +} + +export interface User { + id: string; + isDeleted: boolean; + name?: string | null; + age?: number | null; + userName: string; + icon?: string | null; + nick?: string | null; + email?: string | null; + ip?: string | null; + address?: string | null; + phone?: number | null; + introduction?: string | null; + remark?: string | null; + sex: string; // SexEnum + deptId?: string | null; + creationTime: string; + creatorId?: string | null; + lastModifierId?: string | null; + lastModificationTime?: string | null; + orderNum: number; + state: boolean; + deptName?: string | null; + posts?: Post[]; + roles?: Role[]; + dept?: Dept | null; +} + +export interface Post { + postId: number; + postCode: string; + postName: string; + postSort: number; + status: string; + remark: string; + createTime: string; +} + +/** + * @description 用户信息 + * @param user 用户个人信息 + * @param roleIds 角色IDS 不传id为空 + * @param roles 所有的角色 + * @param postIds 岗位IDS 不传id为空 + * @param posts 所有的岗位 + */ +export interface UserInfoResponse { + user?: User; + roleIds?: string[]; + roles: Role[]; + postIds?: number[]; + posts?: Post[]; +} + +/** + * @description: 部门树 + */ +export interface DeptTreeData { + id: string; + parentId: string; + orderNum: number; + deptName: string; + state: boolean; + children?: DeptTreeData[] | null; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/index.ts new file mode 100644 index 00000000..55e68854 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/index.ts @@ -0,0 +1,103 @@ +import type { GenInfo } from './model'; + +import type { ID, IDS, PageQuery } from '#/api/common'; + +import { ContentTypeEnum } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +enum Api { + batchGenCode = '/tool/gen/batchGenCode', + columnList = '/tool/gen/column', + dataSourceNames = '/tool/gen/getDataNames', + download = '/tool/gen/download', + genCode = '/tool/gen/genCode', + generatedList = '/tool/gen/list', + importTable = '/tool/gen/importTable', + preview = '/tool/gen/preview', + readyToGenList = '/tool/gen/db/list', + root = '/tool/gen', + syncDb = '/tool/gen/synchDb', +} +// 查询代码生成列表 +export function generatedList(params?: PageQuery) { + return requestClient.get(Api.generatedList, { params }); +} + +// 修改代码生成业务 +export function genInfo(tableId: ID) { + return requestClient.get(`${Api.root}/${tableId}`); +} + +// 查询数据库列表 +export function readyToGenList(params?: PageQuery) { + return requestClient.get(Api.readyToGenList, { params }); +} + +// 查询数据表字段列表 +export function columnList(tableId: ID) { + return requestClient.get(`${Api.columnList}/${tableId}`); +} + +/** + * 导入表结构(保存) + * @param tables table名称数组 如sys_a, sys_b + * @param dataName 数据源名称 + * @returns ret + */ +export function importTable(tables: string | string[], dataName: string) { + return requestClient.postWithMsg( + Api.importTable, + { dataName, tables }, + { + headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, + }, + ); +} + +// 修改保存代码生成业务 +export function editSave(data: any) { + return requestClient.putWithMsg(Api.root, data); +} + +// 删除代码生成 +export function genRemove(tableIds: IDS) { + return requestClient.deleteWithMsg(`${Api.root}/${tableIds}`); +} + +// 预览代码 +export function previewCode(tableId: ID) { + return requestClient.get<{ [key: string]: string }>( + `${Api.preview}/${tableId}`, + ); +} + +// 生成代码(下载方式) +export function genDownload(tableId: ID) { + return requestClient.get(`${Api.download}/${tableId}`); +} + +// 生成代码(自定义路径) +export function genWithPath(tableId: ID) { + return requestClient.get(`${Api.genCode}/${tableId}`); +} + +// 同步数据库 +export function syncDb(tableId: ID) { + return requestClient.get(`${Api.syncDb}/${tableId}`, { + successMessageMode: 'message', + }); +} + +// 批量生成代码 +export function batchGenCode(tableIdStr: ID | IDS) { + return requestClient.get(Api.batchGenCode, { + isTransformResponse: false, + params: { tableIdStr }, + responseType: 'blob', + }); +} + +// 查询数据源名称列表 +export function getDataSourceNames() { + return requestClient.get(Api.dataSourceNames); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/model.d.ts new file mode 100644 index 00000000..b859c8ff --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/tool/gen/model.d.ts @@ -0,0 +1,187 @@ +export interface Column { + createDept?: any; + createBy?: any; + createTime?: any; + updateBy?: any; + updateTime?: any; + columnId: string; + tableId: string; + columnName: string; + columnComment: string; + columnType: string; + javaType: string; + javaField: string; + isPk: string; + isIncrement: string; + isRequired: string; + isInsert?: any; + isEdit: string; + isList: string; + isQuery?: any; + queryType: string; + htmlType: string; + dictType: string; + sort: number; + list: boolean; + required: boolean; + pk: boolean; + insert: boolean; + edit: boolean; + usableColumn: boolean; + superColumn: boolean; + increment: boolean; + query: boolean; + capJavaField: string; +} + +export interface Table { + createDept?: any; + createBy?: any; + createTime?: any; + updateBy?: any; + updateTime?: any; + tableId: string; + dataName: string; + tableName: string; + tableComment: string; + subTableName?: any; + subTableFkName?: any; + className: string; + tplCategory: string; + packageName: string; + moduleName: string; + businessName: string; + functionName: string; + functionAuthor: string; + genType?: any; + genPath?: any; + pkColumn?: any; + columns: Column[]; + options?: any; + remark?: any; + treeCode?: any; + treeParentCode?: any; + treeName?: any; + menuIds?: any; + parentMenuId?: any; + parentMenuName?: any; + tree: boolean; + crud: boolean; +} + +export interface Row { + createDept: number; + createBy: number; + createTime: string; + updateBy: number; + updateTime: string; + columnId: string; + tableId: string; + columnName: string; + columnComment: string; + columnType: string; + javaType: string; + javaField: string; + isPk: string; + isIncrement: string; + isRequired: string; + isInsert?: any; + isEdit: string; + isList: string; + isQuery?: any; + queryType: string; + htmlType: string; + dictType: string; + sort: number; + list: boolean; + required: boolean; + pk: boolean; + insert: boolean; + edit: boolean; + usableColumn: boolean; + superColumn: boolean; + increment: boolean; + query: boolean; + capJavaField: string; +} + +export interface Column { + createDept?: any; + createBy?: any; + createTime?: any; + updateBy?: any; + updateTime?: any; + columnId: string; + tableId: string; + columnName: string; + columnComment: string; + columnType: string; + javaType: string; + javaField: string; + isPk: string; + isIncrement: string; + isRequired: string; + isInsert?: any; + isEdit: string; + isList: string; + isQuery?: any; + queryType: string; + htmlType: string; + dictType: string; + sort: number; + list: boolean; + required: boolean; + pk: boolean; + insert: boolean; + edit: boolean; + usableColumn: boolean; + superColumn: boolean; + increment: boolean; + query: boolean; + capJavaField: string; +} + +export interface Info { + createDept?: any; + createBy?: any; + createTime?: any; + updateBy?: any; + updateTime?: any; + tableId: string; + dataName: string; + tableName: string; + tableComment: string; + subTableName?: any; + subTableFkName?: any; + className: string; + tplCategory: string; + packageName: string; + moduleName: string; + businessName: string; + functionName: string; + functionAuthor: string; + genType: string; + genPath: string; + pkColumn?: any; + columns: Column[]; + options?: any; + remark?: any; + treeCode?: any; + treeParentCode?: any; + treeName?: any; + menuIds?: any; + parentMenuId?: any; + parentMenuName?: any; + tree: boolean; + crud: boolean; + // 树表需要添加此属性 + params?: any; + popupComponent?: string; + formComponent?: string; +} + +export interface GenInfo { + tables: Table[]; + rows: Row[]; + info: Info; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/index.ts new file mode 100644 index 00000000..8126effd --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/index.ts @@ -0,0 +1,63 @@ +import type { + CategoryForm, + CategoryQuery, + CategoryTree, + CategoryVO, +} from './model'; + +import type { ID, IDS } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +/** + * 获取流程分类树列表 + * @returns tree + */ +export function categoryTree() { + return requestClient.get('/workflow/category/categoryTree'); +} + +/** + * 查询流程分类列表 + * @param params + * @returns 流程分类列表 + */ +export function categoryList(params?: CategoryQuery) { + return requestClient.get(`/workflow/category/list`, { params }); +} + +/** + * 查询流程分类详情 + * @param id id + * @returns 流程分类详情 + */ +export function categoryInfo(id: ID) { + return requestClient.get(`/workflow/category/${id}`); +} + +/** + * 新增流程分类 + * @param data + * @returns void + */ +export function categoryAdd(data: CategoryForm) { + return requestClient.postWithMsg('/workflow/category', data); +} + +/** + * 更新流程分类 + * @param data + * @returns void + */ +export function categoryUpdate(data: CategoryForm) { + return requestClient.putWithMsg('/workflow/category', data); +} + +/** + * 删除流程分类 + * @param id id + * @returns void + */ +export function categoryRemove(id: ID | IDS) { + return requestClient.deleteWithMsg(`/workflow/category/${id}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/model.d.ts new file mode 100644 index 00000000..7d28c1a6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/category/model.d.ts @@ -0,0 +1,97 @@ +import type { BaseEntity } from '#/api/common'; + +export interface CategoryVO { + /** + * 主键 + */ + id: number | string; + + /** + * 分类名称 + */ + categoryName: string; + + /** + * 分类编码 + */ + categoryCode: string; + + /** + * 父级id + */ + parentId: number | string; + + /** + * 排序 + */ + sortNum: number; + + /** + * 子对象 + */ + children: CategoryVO[]; + key: string; +} + +export interface CategoryForm extends BaseEntity { + /** + * 主键 + */ + id?: number | string; + + /** + * 分类名称 + */ + categoryName?: string; + + /** + * 分类编码 + */ + categoryCode?: string; + + /** + * 父级id + */ + parentId?: number | string; + + /** + * 排序 + */ + sortNum?: number; +} + +export interface CategoryQuery { + /** + * 分类名称 + */ + categoryName?: string; + + /** + * 分类编码 + */ + categoryCode?: string; + + /** + * 父级id + */ + parentId?: number | string; + + /** + * 排序 + */ + sortNum?: number; + + /** + * 日期范围参数 + */ + params?: any; +} + +export interface CategoryTree { + id: number; + parentId: number; + label: string; + weight: number; + children: CategoryTree[]; + key: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/index.ts new file mode 100644 index 00000000..e21255c3 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/index.ts @@ -0,0 +1,155 @@ +import type { ProcessDefinition } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +/** + * 全部的流程定义 + * @param params 查询参数 + * @returns 分页 + */ +export function workflowDefinitionList(params?: PageQuery) { + return requestClient.get>( + '/workflow/definition/list', + { params }, + ); +} + +/** + * 未发布的流程定义 + * @param params 查询参数 + * @returns 分页 + */ +export function unPublishList(params?: PageQuery) { + return requestClient.get>( + '/workflow/definition/unPublishList', + { params }, + ); +} + +/** + * 获取历史流程定义列表 + * @param flowCode + * @returns ProcessDefinition[] + */ +export function getHisListByKey(flowCode: string) { + return requestClient.get( + `/workflow/definition/getHisListByKey/${flowCode}`, + ); +} + +/** + * 获取流程定义详细信息 + * @param id id + * @returns ProcessDefinition + */ +export function workflowDefinitionInfo(id: ID) { + return requestClient.get(`/workflow/definition/${id}`); +} + +/** + * 新增流程定义 + * @param data + */ +export function workflowDefinitionAdd(data: any) { + return requestClient.postWithMsg('/workflow/definition', data); +} + +/** + * 更新流程定义 + * @param data + */ +export function workflowDefinitionUpdate(data: any) { + return requestClient.putWithMsg('/workflow/definition', data); +} + +/** + * 发布流程定义 + * @param id id + * @returns boolean + */ +export function workflowDefinitionPublish(id: ID) { + return requestClient.putWithMsg( + `/workflow/definition/publish/${id}`, + ); +} + +/** + * 取消发布流程定义 + * @param id id + * @returns boolean + */ +export function workflowDefinitionUnPublish(id: ID) { + return requestClient.putWithMsg( + `/workflow/definition/unPublish/${id}`, + ); +} + +/** + * 删除流程定义 + * @param ids idList + */ +export function workflowDefinitionDelete(ids: IDS) { + return requestClient.deleteWithMsg(`/workflow/definition/${ids}`); +} + +/** + * 复制流程定义 + * @param id id + */ +export function workflowDefinitionCopy(id: ID) { + return requestClient.postWithMsg(`/workflow/definition/copy/${id}`); +} + +/** + * 导入流程定义 + * @returns boolean + */ +export function workflowDefinitionImport(data: { + category: ID; + file: Blob | File; +}) { + return requestClient.postWithMsg( + '/workflow/definition/importDef', + data, + { headers: { 'Content-Type': 'multipart/form-data' } }, + ); +} + +/** + * 导出流程定义 + * @param id id + * @returns blob + */ +export function workflowDefinitionExport(id: ID) { + return requestClient.postWithMsg( + `/workflow/definition/exportDef/${id}`, + {}, + { + responseType: 'blob', + isTransformResponse: false, + }, + ); +} + +/** + * 获取流程定义xml字符串 + * @param id id + * @returns xml + */ +export function workflowDefinitionXml(id: ID) { + return requestClient.get(`/workflow/definition/xmlString/${id}`); +} + +/** + * 激活/挂起流程定义 + * @param id 流程定义id + * @param active 激活/挂起 + * @returns boolean + */ +export function workflowDefinitionActive(id: ID, active: boolean) { + return requestClient.putWithMsg( + `/workflow/definition/active/${id}?active=${active}`, + ); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/model.d.ts new file mode 100644 index 00000000..b5eeed9c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/definition/model.d.ts @@ -0,0 +1,19 @@ +export interface ProcessDefinition { + id: string; + createTime: string; + updateTime: string; + tenantId: string; + delFlag: string; + flowCode: string; + flowName: string; + category: string; + categoryName: string; + version: string; + isPublish: number; + formCustom: string; + formPath: string; + activityStatus: number; + listenerType?: any; + listenerPath?: any; + ext?: any; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/index.ts new file mode 100644 index 00000000..ed3523d1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/index.ts @@ -0,0 +1,120 @@ +import type { TaskInfo } from '../task/model'; +import type { FlowInfoResponse } from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +/** + * @param businessId 业务ID + * @returns TaskInfo + */ +export function getTaskByBusinessId(businessId: string) { + return requestClient.get( + `/workflow/instance/getInfo/${businessId}`, + ); +} + +/** + * 分页查询正在运行的流程实例 + * @param params + * @returns + */ +export function pageByRunning(params?: PageQuery) { + return requestClient.get('/workflow/instance/pageByRunning', { params }); +} + +/** + * pageByFinish + * @param params + * @returns + */ +export function pageByFinish(params?: PageQuery) { + return requestClient.get('/workflow/instance/pageByFinish', { params }); +} + +/** + * 按照业务id删除流程实例 + * @param businessIds 业务id + */ +export function deleteByBusinessIds(businessIds: IDS) { + return requestClient.deleteWithMsg( + `/workflow/instance/deleteByBusinessIds${businessIds}`, + ); +} + +/** + * 按照实例id删除流程实例 + * @param instanceIds 实例id + */ +export function deleteByInstanceIds(instanceIds: IDS) { + return requestClient.deleteWithMsg( + `/workflow/instance/deleteByInstanceIds/${instanceIds}`, + ); +} + +/** + * 撤销流程 + * @param data + */ +export function cancelProcessApply(data: { businessId: ID; message?: string }) { + return requestClient.putWithMsg( + '/workflow/instance/cancelProcessApply', + data, + ); +} + +/** + * 激活/挂起流程实例 + * @param instanceId + * @param active + */ +export function workflowInstanceActive(instanceId: ID, active: boolean) { + return requestClient.putWithMsg( + `/workflow/instance/active/${instanceId}?active=${active}`, + ); +} + +/** + * 获取当前登录人发起的流程实例 + * @param params + * @returns PageResult + */ +export function pageByCurrent(params?: PageQuery) { + return requestClient.get>( + '/workflow/instance/pageByCurrent', + { params }, + ); +} + +/** + * 获取流程图,流程记录 + * @param businessId 业务标识 + * @returns 流程图,流程记录 + */ +export function flowInfo(businessId: string) { + return requestClient.get( + `/workflow/instance/flowHisTaskList/${businessId}`, + ); +} + +/** + * 获取流程变量 + * @param instanceId + * @returns Map + */ +export function instanceVariable(instanceId: string) { + return requestClient.get>( + `/workflow/instance/variable/${instanceId}`, + ); +} + +/** + * 作废流程 + */ +export function workflowInstanceInvalid(data: { + comment?: string; + id: string; +}) { + return requestClient.postWithMsg('/workflow/instance/invalid', data); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/model.d.ts new file mode 100644 index 00000000..133d06a3 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/instance/model.d.ts @@ -0,0 +1,41 @@ +export interface Flow { + id: string; + createTime: string; + updateTime: string; + tenantId: string; + delFlag: string; + definitionId: string; + flowName?: any; + instanceId: string; + taskId: string; + cooperateType: number; + cooperateTypeName: string; + businessId?: any; + nodeCode: string; + nodeName: string; + nodeType: number; + targetNodeCode: string; + targetNodeName: string; + approver: string; + approveName: string; + collaborator?: any; + permissionList?: any; + skipType: string; + flowStatus: string; + flowTaskStatus?: any; + flowStatusName?: any; + message: string; + ext: null | string; + createBy?: any; + formCustom: string; + formPath: string; + flowCode?: any; + version?: any; + runDuration: string; + nickName?: any; +} + +export interface FlowInfoResponse { + instanceId: string; + list: Flow[]; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/index.ts new file mode 100644 index 00000000..e3689c4f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/index.ts @@ -0,0 +1,172 @@ +import type { + CompleteTaskReqData, + NextNodeInfo, + StartWorkFlowReqData, + TaskInfo, + TaskOperationData, + TaskOperationType, +} from './model'; + +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +/** + * 启动任务 + * @param data + */ +export function startWorkFlow(data: StartWorkFlowReqData) { + return requestClient.post<{ + processInstanceId: string; + taskId: string; + }>('/workflow/task/startWorkFlow', data); +} + +/** + * 办理任务 + * @param data + */ +export function completeTask(data: CompleteTaskReqData) { + return requestClient.postWithMsg('/workflow/task/completeTask', data); +} + +/** + * 查询当前用户的待办任务 + * @param params + */ +export function pageByTaskWait(params?: PageQuery) { + return requestClient.get>( + '/workflow/task/pageByTaskWait', + { params }, + ); +} + +/** + * 查询当前用户的已办任务 + * @param params + */ +export function pageByTaskFinish(params?: PageQuery) { + return requestClient.get>( + '/workflow/task/pageByTaskFinish', + { params }, + ); +} + +/** + * 查询所有待办任务 + * @param params + */ +export function pageByAllTaskWait(params?: PageQuery) { + return requestClient.get>( + '/workflow/task/pageByAllTaskWait', + { params }, + ); +} + +/** + * 查询已办任务 + * @param params + */ +export function pageByAllTaskFinish(params?: PageQuery) { + return requestClient.get>( + '/workflow/task/pageByAllTaskFinish', + { params }, + ); +} + +/** + * 查询当前用户的抄送 + * @param params + */ +export function pageByTaskCopy(params?: PageQuery) { + return requestClient.get>( + '/workflow/task/pageByTaskCopy', + { params }, + ); +} + +/** + * 根据taskId查询代表任务 + * @param taskId 任务id + * @returns info + */ +export function getTaskByTaskId(taskId: string) { + return requestClient.get(`/workflow/task/getTask/${taskId}`); +} + +/** + * 终止任务 + */ +export function terminationTask(data: { comment?: string; taskId: string }) { + return requestClient.postWithMsg( + '/workflow/task/terminationTask', + data, + ); +} + +/** + * 任务操作 + * @param taskOperationData 参数 + * @param taskOperation 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature + */ +export function taskOperation( + taskOperationData: TaskOperationData, + taskOperation: TaskOperationType, +) { + return requestClient.postWithMsg( + `/workflow/task/taskOperation/${taskOperation}`, + taskOperationData, + ); +} + +/** + * 修改任务办理人 + * @param taskIdList 任务id + * @param userId 办理人id + */ +export function updateAssignee(taskIdList: IDS, userId: ID) { + return requestClient.putWithMsg( + `/workflow/task/updateAssignee/${userId}`, + taskIdList, + ); +} + +/** + * 驳回审批 + * @param data 参数 + */ +export function backProcess(data: any) { + return requestClient.postWithMsg('/workflow/task/backProcess', data); +} + +/** + * 获取可驳回节点 + * @param definitionId 流程定义ID + * @param nodeCode 当前节点编码 + */ +export function getBackTaskNode(definitionId: string, nodeCode: string) { + return requestClient.get<{ nodeCode: string; nodeName: string }[]>( + `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`, + ); +} + +/** + * 获取当前任务的所有办理人 + * @param taskId 任务id + */ +export function currentTaskAllUser(taskId: ID) { + return requestClient.get(`/workflow/task/currentTaskAllUser/${taskId}`); +} + +/** + * 获取下一节点 + * @param data data + * @param data.taskId taskId + * @returns NextNodeInfo + */ +export function getNextNodeList(data: { taskId: string }) { + return requestClient.post( + '/workflow/task/getNextNodeList', + data, + ); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/model.d.ts new file mode 100644 index 00000000..5f638a8f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/workflow/task/model.d.ts @@ -0,0 +1,108 @@ +export interface ButtonWithPermission { + code: string; + value: null | string; + show: boolean; +} + +export interface TaskInfo { + id: string; + categoryName: string; + createTime: string; + updateTime: string; + tenantId: string; + delFlag?: any; + definitionId: string; + instanceId: string; + flowName: string; + businessId: string; + nodeCode: string; + nodeName: string; + nodeType: number; + permissionList?: any; + userList?: any; + formCustom: string; + formPath?: any; + flowCode: string; + version: string; + flowStatus: string; + flowStatusName: string; + assigneeIds: string; + assigneeNames: string; + processedBy: string; + type: string; + nodeRatio?: string; + createBy: string; + createByName: string; + targetNodeName?: string; + buttonList: ButtonWithPermission[]; +} + +export interface CompleteTaskReqData { + messageType: string[]; + flowCopyList: { userId: string; userName: string }[]; + taskId: ID; + taskVariables: Record; + variables: any; + // 附件ID 1,2,3,4形式 + fileId?: string; + // 选人 key为节点code value为用户ID join(,) + assigneeMap: { [key: string]: string }; +} + +export interface StartWorkFlowReqData { + /** + * 业务ID + */ + businessId: ID; + /** + * flowCode + */ + flowCode: string; + /** + * 流程变量 + */ + variables: Record; +} + +export interface TaskOperationData { + message?: string; + taskId: ID; + // 单个操作人 + userId?: ID; + // 多个操作人 + userIds?: IDS; +} + +/** + * 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature + */ +export type TaskOperationType = + | 'addSignature' + | 'delegateTask' + | 'reductionSignature' + | 'transferTask'; + +export interface NextNodeInfo { + skipList: string[]; + id: string; + createTime: string; + updateTime: string; + tenantId: string; + delFlag: string; + nodeType: number; + definitionId: string; + nodeCode: string; + nodeName: string; + permissionFlag: string; + nodeRatio: string; + coordinate: string; + version: string; + anyNodeSkip: any; + listenerType: any; + listenerPath: any; + handlerType: any; + handlerPath: any; + formCustom: string; + formPath: any; + ext: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/app.vue b/Yi.Vben5.Vue3/apps/web-antd/src/app.vue new file mode 100644 index 00000000..ecdd2242 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/app.vue @@ -0,0 +1,43 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/bootstrap.ts b/Yi.Vben5.Vue3/apps/web-antd/src/bootstrap.ts new file mode 100644 index 00000000..a6d65a40 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/bootstrap.ts @@ -0,0 +1,79 @@ +import { createApp, watchEffect } from 'vue'; + +import { registerAccessDirective } from '@vben/access'; +import { registerLoadingDirective } from '@vben/common-ui/es/loading'; +import { preferences } from '@vben/preferences'; +import { initStores } from '@vben/stores'; +import '@vben/styles'; +import '@vben/styles/antd'; + +import { useTitle } from '@vueuse/core'; + +import { setupGlobalComponent } from '#/components/global'; +import { $t, setupI18n } from '#/locales'; + +import { initComponentAdapter } from './adapter/component'; +import { initSetupVbenForm } from './adapter/form'; +import App from './app.vue'; +import { router } from './router'; + +async function bootstrap(namespace: string) { + // 初始化组件适配器 + await initComponentAdapter(); + + // 初始化表单组件 + await initSetupVbenForm(); + + // // 设置弹窗的默认配置 + // setDefaultModalProps({ + // fullscreenButton: false, + // }); + // // 设置抽屉的默认配置 + // setDefaultDrawerProps({ + // zIndex: 1020, + // }); + + const app = createApp(App); + + // 全局组件 + setupGlobalComponent(app); + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + + // 国际化 i18n 配置 + await setupI18n(app); + + // 配置 pinia-tore + await initStores(app, { namespace }); + + // 安装权限指令 + registerAccessDirective(app); + + // 初始化 tippy + const { initTippy } = await import('@vben/common-ui/es/tippy'); + initTippy(app); + + // 配置路由及路由守卫 + app.use(router); + + // 配置Motion插件 + const { MotionPlugin } = await import('@vben/plugins/motion'); + app.use(MotionPlugin); + + // 动态更新标题 + watchEffect(() => { + if (preferences.app.dynamicTitle) { + const routeTitle = router.currentRoute.value.meta?.title; + const pageTitle = + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; + useTitle(pageTitle); + } + }); + + app.mount('#app'); +} + +export { bootstrap }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/index.ts new file mode 100644 index 00000000..6afad017 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/index.ts @@ -0,0 +1,3 @@ +export { default as CropperAvatar } from './src/cropper-avatar.vue'; +export { default as CropperImage } from './src/cropper.vue'; +export type { Cropper } from './src/typing'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-avatar.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-avatar.vue new file mode 100644 index 00000000..a29aab2e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-avatar.vue @@ -0,0 +1,170 @@ + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-modal.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-modal.vue new file mode 100644 index 00000000..e7daa940 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper-modal.vue @@ -0,0 +1,382 @@ + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper.vue new file mode 100644 index 00000000..8dd30001 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/cropper.vue @@ -0,0 +1,207 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/typing.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/typing.ts new file mode 100644 index 00000000..e76cc6f8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/cropper/src/typing.ts @@ -0,0 +1,8 @@ +import type Cropper from 'cropperjs'; + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export type { Cropper }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/description/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/index.ts new file mode 100644 index 00000000..46a851f1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/index.ts @@ -0,0 +1,3 @@ +export { default as Description } from './src/description.vue'; +export * from './src/typing'; +export { useDescription } from './src/useDescription'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/description.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/description.vue new file mode 100644 index 00000000..47adeb83 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/description.vue @@ -0,0 +1,205 @@ + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/typing.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/typing.ts new file mode 100644 index 00000000..204b6971 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/typing.ts @@ -0,0 +1,48 @@ +import type { DescriptionsProps } from 'ant-design-vue/es/descriptions'; +import type { JSX } from 'vue/jsx-runtime'; + +import type { CSSProperties, VNode } from 'vue'; + +import type { Recordable } from '@vben/types'; + +export interface DescItem { + labelMinWidth?: number; + contentMinWidth?: number; + labelStyle?: CSSProperties; + field: string; + label: JSX.Element | string | VNode; + // Merge column + span?: number; + show?: (...arg: any) => boolean; + // render + render?: ( + val: any, + data: Recordable, + ) => Element | JSX.Element | number | string | undefined | VNode; +} + +export interface DescriptionProps extends DescriptionsProps { + // Whether to include the collapse component + useCollapse?: boolean; + /** + * item configuration + * @type DescItem + */ + schema: DescItem[]; + /** + * 数据 + * @type object + */ + data: Recordable; +} + +export interface DescInstance { + setDescProps(descProps: Partial, delay?: boolean): void; +} + +export type Register = (descInstance: DescInstance) => void; + +/** + * @description: + */ +export type UseDescReturnType = [Register, DescInstance]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/useDescription.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/useDescription.ts new file mode 100644 index 00000000..6c5584a7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/description/src/useDescription.ts @@ -0,0 +1,47 @@ +import type { + DescInstance, + DescriptionProps, + UseDescReturnType, +} from './typing'; + +import { getCurrentInstance, ref, unref } from 'vue'; + +/** + * @deprecated 使用antd原生组件替代 下个版本将会移除 + */ +export function useDescription( + props?: Partial, +): UseDescReturnType { + if (!getCurrentInstance()) { + throw new Error( + 'useDescription() can only be used inside setup() or functional components!', + ); + } + const desc = ref(null); + const loaded = ref(false); + + function register(instance: DescInstance) { + // if (unref(loaded) && import.meta.env.PROD) { + // return; + // } + desc.value = instance; + props && instance.setDescProps(props); + loaded.value = true; + } + + const methods: DescInstance = { + setDescProps: ( + descProps: Partial, + delay = false, + ): void => { + if (!delay) { + unref(desc)?.setDescProps(descProps); + return; + } + // 奇怪的问题 在modal中需要setTimeout才会生效 + setTimeout(() => unref(desc)?.setDescProps(descProps)); + }, + }; + + return [register, methods]; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/index.ts new file mode 100644 index 00000000..08282cf4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/index.ts @@ -0,0 +1,2 @@ +export { tagSelectOptions, tagTypes } from './src/data'; +export { default as DictTag } from './src/index.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/data.tsx b/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/data.tsx new file mode 100644 index 00000000..7033c25e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/data.tsx @@ -0,0 +1,44 @@ +import type { VNode } from 'vue'; + +import { Tag } from 'ant-design-vue'; + +interface TagType { + [key: string]: { color: string; label: string }; +} + +export const tagTypes: TagType = { + cyan: { color: 'cyan', label: 'cyan' }, + danger: { color: 'error', label: '危险(danger)' }, + /** 由于和elementUI不同 用于替换颜色 */ + default: { color: 'default', label: '默认(default)' }, + green: { color: 'green', label: 'green' }, + info: { color: 'default', label: '信息(info)' }, + orange: { color: 'orange', label: 'orange' }, + /** 自定义预设 color可以为16进制颜色 */ + pink: { color: 'pink', label: 'pink' }, + primary: { color: 'processing', label: '主要(primary)' }, + purple: { color: 'purple', label: 'purple' }, + red: { color: 'red', label: 'red' }, + success: { color: 'success', label: '成功(success)' }, + warning: { color: 'warning', label: '警告(warning)' }, +}; + +// 字典选择使用 { label: string; value: string }[] +interface Options { + label: string | VNode; + value: string; +} + +export function tagSelectOptions() { + const selectArray: Options[] = []; + Object.keys(tagTypes).forEach((key) => { + if (!tagTypes[key]) return; + const label = tagTypes[key].label; + const color = tagTypes[key].color; + selectArray.push({ + label: {label}, + value: key, + }); + }); + return selectArray; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/index.vue new file mode 100644 index 00000000..1edd1274 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/dict/src/index.vue @@ -0,0 +1,62 @@ + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/global/button.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/global/button.ts new file mode 100644 index 00000000..397fdf61 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/global/button.ts @@ -0,0 +1,21 @@ +import { defineComponent, h } from 'vue'; + +import { Button } from 'ant-design-vue'; +import buttonProps from 'ant-design-vue/es/button/buttonTypes'; +import { omit } from 'lodash-es'; + +/** + * 表格操作列按钮专用 + */ +export const GhostButton = defineComponent({ + name: 'GhostButton', + props: omit(buttonProps(), ['type', 'ghost', 'size']), + setup(props, { attrs, slots }) { + return () => + h( + Button, + { ...props, ...attrs, type: 'primary', ghost: true, size: 'small' }, + slots, + ); + }, +}); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/global/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/global/index.ts new file mode 100644 index 00000000..ff07852f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/global/index.ts @@ -0,0 +1,14 @@ +import type { App } from 'vue'; + +import { Button as AButton } from 'ant-design-vue'; + +import { GhostButton } from './button'; + +/** + * 全局组件注册 + */ +export function setupGlobalComponent(app: App) { + app.use(AButton); + // 表格操作列专用按钮 + app.component('GhostButton', GhostButton); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/table/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/table/index.ts new file mode 100644 index 00000000..03a735c8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/table/index.ts @@ -0,0 +1,2 @@ +export { default as OptionsTag } from './src/options-tag.vue'; +export { default as TableSwitch } from './src/table-switch.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/options-tag.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/options-tag.vue new file mode 100644 index 00000000..c7c6891d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/options-tag.vue @@ -0,0 +1,21 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/table-switch.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/table-switch.vue new file mode 100644 index 00000000..f4106304 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/table/src/table-switch.vue @@ -0,0 +1,134 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/index.ts new file mode 100644 index 00000000..d82e6021 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/index.ts @@ -0,0 +1 @@ +export { default as TenantToggle } from './src/index.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/src/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/src/index.vue new file mode 100644 index 00000000..23b10751 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tenant-toggle/src/index.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/index.ts new file mode 100644 index 00000000..6fe80740 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/index.ts @@ -0,0 +1 @@ +export { default as Tinymce } from './src/editor.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/editor.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/editor.vue new file mode 100644 index 00000000..2294ce70 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/editor.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/tinymce.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/tinymce.ts new file mode 100644 index 00000000..eb3964a6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tinymce/src/tinymce.ts @@ -0,0 +1,11 @@ +// Any plugins you want to setting has to be imported +// Detail plugins list see https://www.tinymce.com/docs/plugins/ +// Custom builds see https://www.tinymce.com/download/custom-builds/ +// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration + +// quickbars 快捷栏 +export const plugins = + 'preview importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap emoticons accordion'; + +export const toolbar = + 'undo redo | accordion accordionremove | blocks fontfamily fontsize | bold italic underline strikethrough | align numlist bullist | link image | table media | lineheight outdent indent| forecolor backcolor removeformat | charmap emoticons | code fullscreen preview | save print | pagebreak anchor codesample | ltr rtl'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/index.ts new file mode 100644 index 00000000..f142790a --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/index.ts @@ -0,0 +1,2 @@ +export { default as MenuSelectTable } from './src/menu-select-table.vue'; +export { default as TreeSelectPanel } from './src/tree-select-panel.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/data.tsx b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/data.tsx new file mode 100644 index 00000000..023ad60c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/data.tsx @@ -0,0 +1,98 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; +import type { ID } from '#/api/common'; +import type { MenuOption } from '#/api/system/menu/model'; + +import { h, markRaw } from 'vue'; + +import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons'; + +export interface Permission { + checked: boolean; + id: ID; + label: string; +} + +export interface MenuPermissionOption extends MenuOption { + permissions: Permission[]; +} + +// (M目录 C菜单 F按钮) +// 支持多种格式的菜单类型值 +const menuTypes: Record; value: string }> = { + c: { icon: markRaw(MenuIcon), value: '菜单' }, + menu: { icon: markRaw(MenuIcon), value: '菜单' }, + Menu: { icon: markRaw(MenuIcon), value: '菜单' }, + catalog: { icon: markRaw(FolderIcon), value: '目录' }, + directory: { icon: markRaw(FolderIcon), value: '目录' }, + folder: { icon: markRaw(FolderIcon), value: '目录' }, + m: { icon: markRaw(FolderIcon), value: '目录' }, + catalogue: { icon: markRaw(FolderIcon), value: '目录' }, + Catalogue: { icon: markRaw(FolderIcon), value: '目录' }, + component: { icon: markRaw(OkButtonIcon), value: '按钮' }, + Component: { icon: markRaw(OkButtonIcon), value: '按钮' }, + f: { icon: markRaw(OkButtonIcon), value: '按钮' }, + button: { icon: markRaw(OkButtonIcon), value: '按钮' }, +}; + +export const nodeOptions = [ + { label: '节点关联', value: true }, + { label: '节点独立', value: false }, +]; + +export const columns: VxeGridProps['columns'] = [ + { + type: 'checkbox', + title: '菜单名称', + field: 'menuName', + treeNode: true, + headerAlign: 'left', + align: 'left', + width: 230, + }, + { + title: '图标', + field: 'menuIcon', + width: 80, + slots: { + default: ({ row }) => { + if (row?.menuIcon === '#' || !row?.menuIcon) { + return ''; + } + return ( + + + + ); + }, + }, + }, + { + title: '类型', + field: 'menuType', + width: 80, + slots: { + default: ({ row }) => { + const typeKey = `${row.menuType ?? ''}`.toString().trim().toLowerCase(); + const current = menuTypes[typeKey]; + if (!current) { + return '未知'; + } + return ( + + {h(current.icon, { class: 'size-[18px]' })} + {current.value} + + ); + }, + }, + }, + { + title: '权限标识', + field: 'permissions', + headerAlign: 'left', + align: 'left', + slots: { + default: 'permissions', + }, + }, +]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/helper.tsx b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/helper.tsx new file mode 100644 index 00000000..5fc80f46 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/helper.tsx @@ -0,0 +1,206 @@ +import type { MenuPermissionOption } from './data'; + +import type { useVbenVxeGrid } from '#/adapter/vxe-table'; +import type { MenuOption } from '#/api/system/menu/model'; + +import { eachTree, treeToList } from '@vben/utils'; + +import { notification } from 'ant-design-vue'; +import { difference, isEmpty, isUndefined } from 'lodash-es'; + +/** + * 权限列设置是否全选 + * @param record 行记录 + * @param checked 是否选中 + */ +export function setPermissionsChecked( + record: MenuPermissionOption, + checked: boolean, +) { + if (record?.permissions?.length > 0) { + // 全部设置为选中 + record.permissions.forEach((permission) => { + permission.checked = checked; + }); + } +} + +/** + * 设置当前行 & 所有子节点选中状态 + * @param record 行 + * @param checked 是否选中 + */ +export function rowAndChildrenChecked( + record: MenuPermissionOption, + checked: boolean, +) { + // 当前行选中 + setPermissionsChecked(record, checked); + // 所有子节点选中 + record?.children?.forEach?.((permission) => { + rowAndChildrenChecked(permission as MenuPermissionOption, checked); + }); +} + +/** + * void方法 会直接修改原始数据 + * 将树结构转为 tree+permissions结构 + * @param menus 后台返回的menu + */ +export function menusWithPermissions(menus: MenuOption[]) { + eachTree(menus, (item: MenuPermissionOption) => { + validateMenuTree(item); + if (item.children && item.children.length > 0) { + /** + * 所有为按钮的节点提取出来 + * 需要注意 这里需要过滤目录下直接是按钮的情况 + * 将按钮往children添加而非加到permissions + */ + const permissions = item.children.filter( + (child: MenuOption) => + isComponentType(child.menuType) && !isCatalogueType(item.menuType), + ); + // 取差集 + const diffCollection = difference(item.children, permissions); + // 更新后的children 即去除按钮 + item.children = diffCollection; + + // permissions作为字段添加到item + const permissionsArr = permissions.map((permission) => { + return { + id: permission.id, + label: permission.menuName, + checked: false, + }; + }); + item.permissions = permissionsArr; + } + }); +} + +/** + * 设置表格选中 + * @param checkedKeys 选中的keys + * @param menus 菜单 转换后的菜单 + * @param tableApi api + * @param association 是否节点关联 + */ +export function setTableChecked( + checkedKeys: (number | string)[], + menus: MenuPermissionOption[], + tableApi: ReturnType['1'], + association: boolean, +) { + // tree转list + const menuList: MenuPermissionOption[] = treeToList(menus); + // 拿到勾选的行数据 + let checkedRows = menuList.filter((item) => checkedKeys.includes(item.id)); + + /** + * 节点独立切换到节点关联 只需要最末尾的数据 即children为空 + */ + if (!association) { + checkedRows = checkedRows.filter( + (item) => isUndefined(item.children) || isEmpty(item.children), + ); + } + + // 设置行选中 & permissions选中 + checkedRows.forEach((item) => { + tableApi.grid.setCheckboxRow(item, true); + if (item?.permissions?.length > 0) { + item.permissions.forEach((permission) => { + if (checkedKeys.includes(permission.id)) { + permission.checked = true; + } + }); + } + }); + + /** + * 节点独立切换到节点关联 + * 勾选后还需要过滤权限没有任何勾选的情况 这时候取消行的勾选 + */ + if (!association) { + const emptyRows = checkedRows.filter((item) => { + if (isUndefined(item.permissions) || isEmpty(item.permissions)) { + return false; + } + return item.permissions.every( + (permission) => permission.checked === false, + ); + }); + // 设置为不选中 + tableApi.grid.setCheckboxRow(emptyRows, false); + } +} + +/** + * 判断是否为菜单类型(Menu/C) + */ +function isMenuType(menuType: string): boolean { + const type = menuType?.toLowerCase(); + return type === 'c' || type === 'menu'; +} + +/** + * 判断是否为目录类型(Catalogue/M) + */ +function isCatalogueType(menuType: string): boolean { + const type = menuType?.toLowerCase(); + return type === 'm' || type === 'catalogue' || type === 'catalog' || type === 'directory' || type === 'folder'; +} + +/** + * 判断是否为按钮类型(Component/F) + */ +function isComponentType(menuType: string): boolean { + const type = menuType?.toLowerCase(); + return type === 'f' || type === 'component' || type === 'button'; +} + +/** + * 校验是否符合规范 给出warning提示 + * + * 不符合规范 + * 比如: 菜单下放目录 菜单下放菜单 + * 比如: 按钮下放目录 按钮下放菜单 按钮下放按钮 + * @param menu menu + */ +function validateMenuTree(menu: MenuOption) { + // 菜单下不能放目录/菜单 + if (isMenuType(menu.menuType)) { + menu.children?.forEach?.((item) => { + if (isMenuType(item.menuType) || isCatalogueType(item.menuType)) { + const description = `错误用法: [${menu.menuName} - 菜单]下不能放 目录/菜单 -> [${item.menuName}]`; + console.warn(description); + notification.warning({ + message: '提示', + description, + duration: 0, + }); + } + }); + } + // 按钮为最末级 不能再放置 + if (isComponentType(menu.menuType)) { + /** + * 其实可以直接判断length 这里为了更准确知道menuName 采用遍历的形式 + */ + menu.children?.forEach?.((item) => { + if ( + isMenuType(item.menuType) || + isComponentType(item.menuType) || + isCatalogueType(item.menuType) + ) { + const description = `错误用法: [${menu.menuName} - 按钮]下不能放置'目录/菜单/按钮' -> [${item.menuName}]`; + console.warn(description); + notification.warning({ + message: '提示', + description, + duration: 0, + }); + } + }); + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/hook.tsx b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/hook.tsx new file mode 100644 index 00000000..b7a70ad8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/hook.tsx @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { TourProps } from 'ant-design-vue'; + +import { defineComponent, ref } from 'vue'; + +import { useLocalStorage } from '@vueuse/core'; +import { Tour } from 'ant-design-vue'; + +/** + * 全屏引导 + * @returns value + */ +export function useFullScreenGuide() { + const open = ref(false); + /** + * 是否已读 只显示一次 + */ + const read = useLocalStorage('menu_select_fullscreen_read', false); + + function openGuide() { + if (!read.value) { + open.value = true; + } + } + + function closeGuide() { + open.value = false; + read.value = true; + } + + const steps: TourProps['steps'] = [ + { + title: '提示', + description: '点击这里可以全屏', + target: () => + document.querySelector( + 'div#menu-select-table .vxe-tools--operate > button[title="全屏"]', + )!, + }, + ]; + + const FullScreenGuide = defineComponent({ + name: 'FullScreenGuide', + inheritAttrs: false, + setup() { + return () => ( + + ); + }, + }); + + return { + FullScreenGuide, + openGuide, + closeGuide, + }; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/menu-select-table.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/menu-select-table.vue new file mode 100644 index 00000000..c6a5c386 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/menu-select-table.vue @@ -0,0 +1,411 @@ + + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/tree-select-panel.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/tree-select-panel.vue new file mode 100644 index 00000000..321a341b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/tree/src/tree-select-panel.vue @@ -0,0 +1,222 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/index.ts new file mode 100644 index 00000000..6a352f5e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/index.ts @@ -0,0 +1,8 @@ +/** + * @description: 旧版文件上传组件 使用FileUpload代替 + */ +export { default as FileUploadOld } from './src/file-upload.vue'; +/** + * @description: 旧版图片上传组件 使用ImageUpload代替 + */ +export { default as ImageUploadOld } from './src/image-upload.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/file-upload.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/file-upload.vue new file mode 100644 index 00000000..b45ec43d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/file-upload.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/helper.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/helper.ts new file mode 100644 index 00000000..ff70b413 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/helper.ts @@ -0,0 +1,51 @@ +import { fileTypeFromBlob } from '@vben/utils'; + +/** + * 不支持txt文件 @see https://github.com/sindresorhus/file-type/issues/55 + * 需要自行修改 + * @param file file对象 + * @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*) + * @returns 是否通过文件类型校验 + */ +export async function checkFileType(file: File, accepts: string[]) { + if (!accepts || accepts?.length === 0) { + return true; + } + console.log(file); + const fileType = await fileTypeFromBlob(file); + if (!fileType) { + console.error('无法获取文件类型'); + return false; + } + console.log('文件类型', fileType); + // 是否文件拓展名/文件头任意有一个匹配 + return accepts.includes(fileType.ext) || accepts.includes(fileType.mime); +} + +/** + * 默认图片类型 + */ +export const defaultImageAccept = ['jpg', 'jpeg', 'png', 'gif', 'webp']; +/** + * 判断文件类型是否符合要求 + * @param file file对象 + * @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*) + * @returns 是否通过文件类型校验 + */ +export async function checkImageFileType(file: File, accepts: string[]) { + // 空的accepts 使用默认规则 + if (!accepts || accepts.length === 0) { + accepts = defaultImageAccept; + } + const fileType = await fileTypeFromBlob(file); + if (!fileType) { + console.error('无法获取文件类型'); + return false; + } + console.log('文件类型', fileType); + // 是否文件拓展名/文件头任意有一个匹配 + if (accepts.includes(fileType.ext) || accepts.includes(fileType.mime)) { + return true; + } + return false; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/image-upload.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/image-upload.vue new file mode 100644 index 00000000..dc56430c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/image-upload.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/typing.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/typing.ts new file mode 100644 index 00000000..8d728b6c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/typing.ts @@ -0,0 +1,37 @@ +import type { Recordable } from '@vben/types'; + +export enum UploadResultStatus { + DONE = 'done', + ERROR = 'error', + SUCCESS = 'success', + UPLOADING = 'uploading', +} + +export interface FileItem { + thumbUrl?: string; + name: string; + size: number | string; + type?: string; + percent: number; + file: File; + status?: UploadResultStatus; + response?: Recordable | { fileName: string; ossId: string; url: string }; + uuid: string; +} + +export interface Wrapper { + record: FileItem; + uidKey: string; + valueKey: string; +} + +export interface BaseFileItem { + uid: number | string; + url: string; + name?: string; +} +export interface PreviewFileItem { + url: string; + name: string; + type: string; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/use-upload.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/use-upload.ts new file mode 100644 index 00000000..4ae552f3 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload-old/src/use-upload.ts @@ -0,0 +1,61 @@ +import type { Ref } from 'vue'; + +import { computed, unref } from 'vue'; + +import { $t } from '@vben/locales'; + +export function useUploadType({ + acceptRef, + helpTextRef, + maxNumberRef, + maxSizeRef, +}: { + acceptRef: Ref; + helpTextRef: Ref; + maxNumberRef: Ref; + maxSizeRef: Ref; +}) { + // 文件类型限制 + const getAccept = computed(() => { + const accept = unref(acceptRef); + if (accept && accept.length > 0) { + return accept; + } + return []; + }); + const getStringAccept = computed(() => { + return unref(getAccept) + .map((item) => { + return item.indexOf('/') > 0 || item.startsWith('.') + ? item + : `.${item}`; + }) + .join(','); + }); + + // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 + const getHelpText = computed(() => { + const helpText = unref(helpTextRef); + if (helpText) { + return helpText; + } + const helpTexts: string[] = []; + + const accept = unref(acceptRef); + if (accept.length > 0) { + helpTexts.push($t('component.upload.accept', [accept.join(',')])); + } + + const maxSize = unref(maxSizeRef); + if (maxSize) { + helpTexts.push($t('component.upload.maxSize', [maxSize])); + } + + const maxNumber = unref(maxNumberRef); + if (maxNumber && maxNumber !== Infinity) { + helpTexts.push($t('component.upload.maxNumber', [maxNumber])); + } + return helpTexts.join(','); + }); + return { getAccept, getStringAccept, getHelpText }; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/index.ts new file mode 100644 index 00000000..b86d4957 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/index.ts @@ -0,0 +1,2 @@ +export { default as FileUpload } from './src/file-upload.vue'; +export { default as ImageUpload } from './src/image-upload.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/file-upload.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/file-upload.vue new file mode 100644 index 00000000..8873d4d1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/file-upload.vue @@ -0,0 +1,150 @@ + + + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/helper.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/helper.ts new file mode 100644 index 00000000..9b85a533 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/helper.ts @@ -0,0 +1,28 @@ +import type { UploadFile } from 'ant-design-vue'; + +/** + * 默认支持上传的图片文件类型 + */ +export const defaultImageAcceptExts = [ + '.jpg', + '.jpeg', + '.png', + '.gif', + '.webp', +]; + +/** + * 默认支持上传的文件类型 + */ +export const defaultFileAcceptExts = ['.xlsx', '.csv', '.docx', '.pdf']; + +/** + * 文件(非图片)的默认预览逻辑 + * 默认: window.open打开 交给浏览器接管 + * @param file file + */ +export function defaultFilePreview(file: UploadFile) { + if (file?.url) { + window.open(file.url); + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/hook.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/hook.ts new file mode 100644 index 00000000..f7451518 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/hook.ts @@ -0,0 +1,385 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { UploadChangeParam, UploadFile } from 'ant-design-vue'; +import type { FileType } from 'ant-design-vue/es/upload/interface'; +import type { + RcFile, + UploadRequestOption, +} from 'ant-design-vue/es/vc-upload/interface'; + +import type { ModelRef } from 'vue'; + +import type { + BaseUploadProps, + CustomGetter, + UploadEmits, + UploadType, +} from './props'; + +import type { AxiosProgressEvent, UploadResult } from '#/api'; +import type { OssFile } from '#/api/system/oss/model'; + +import { computed, onUnmounted, ref, watch } from 'vue'; + +import { $t } from '@vben/locales'; + +import { message, Modal } from 'ant-design-vue'; +import { isFunction, isString } from 'lodash-es'; + +import { ossInfo } from '#/api/system/oss'; + +/** + * 图片预览hook + * @returns 预览 + */ +export function useImagePreview() { + /** + * 获取base64字符串 + * @param file 文件 + * @returns base64字符串 + */ + function getBase64(file: File) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener('load', () => resolve(reader.result)); + reader.addEventListener('error', (error) => reject(error)); + }); + } + + // Modal可见 + const previewVisible = ref(false); + // 预览的图片 url/base64 + const previewImage = ref(''); + // 预览的图片名称 + const previewTitle = ref(''); + + function handleCancel() { + previewVisible.value = false; + previewTitle.value = ''; + } + + async function handlePreview(file: UploadFile) { + if (!file) { + return; + } + // 文件预览 取base64 + if (!file.url && !file.preview && file.originFileObj) { + file.preview = (await getBase64(file.originFileObj)) as string; + } + // 这里不可能为空 + const url = file.url ?? ''; + previewImage.value = url || file.preview || ''; + previewVisible.value = true; + previewTitle.value = + file.name || url.slice(Math.max(0, url.lastIndexOf('/') + 1)); + } + + return { + previewVisible, + previewImage, + previewTitle, + handleCancel, + handlePreview, + }; +} + +/** + * 图片上传和文件上传的通用hook + * @param props 组件props + * @param emit 事件 + * @param bindValue 双向绑定的idList + * @param uploadType 区分是文件还是图片上传 + * @returns hook + */ +export function useUpload( + props: Readonly, + emit: UploadEmits, + bindValue: ModelRef, + uploadType: UploadType, +) { + // 组件内部维护fileList + const innerFileList = ref([]); + + const acceptStr = computed(() => { + // string类型 + if (isString(props.acceptFormat)) { + return props.acceptFormat; + } + // 函数类型 + if (isFunction(props.acceptFormat)) { + return props.acceptFormat(props.accept!); + } + // 默认 会对拓展名做处理 + return props.accept + ?.split(',') + .map((item) => { + if (item.startsWith('.')) { + return item.slice(1); + } + return item; + }) + .join(', '); + }); + + /** + * 自定义文件显示名称 需要区分不同的接口 + * @param cb callback + * @returns 文件名 + */ + function transformFilename(cb: Parameters>[0]) { + if (isFunction(props.customFilename)) { + return props.customFilename(cb); + } + // info接口 + if (cb.type === 'info') { + return cb.response.originalName; + } + // 上传接口 + return cb.response.fileName; + } + + /** + * 自定义缩略图 需要区分不同的接口 + * @param cb callback + * @returns 缩略图地址 + */ + function transformThumbUrl(cb: Parameters>[0]) { + if (isFunction(props.customThumbUrl)) { + return props.customThumbUrl(cb); + } + // image 默认返回图片链接 + if (uploadType === 'image') { + // info接口 + if (cb.type === 'info') { + return cb.response.url; + } + // 上传接口 + return cb.response.url; + } + // 文件默认返回空 走antd默认的预览图逻辑 + return undefined; + } + + // 用来标识是否为上传 这样在watch内部不需要请求api + let isUpload = false; + function handleChange(info: UploadChangeParam) { + /** + * 移除当前文件 + * @param currentFile 当前文件 + * @param currentFileList 当前所有文件list + */ + function removeCurrentFile( + currentFile: UploadChangeParam['file'], + currentFileList: UploadChangeParam['fileList'], + ) { + if (props.removeOnError) { + currentFileList.splice(currentFileList.indexOf(currentFile), 1); + } else { + currentFile.status = 'error'; + } + } + + const { file: currentFile, fileList } = info; + + switch (currentFile.status) { + // 上传成功 只是判断httpStatus 200 需要手动判断业务code + case 'done': { + if (!currentFile.response) { + return; + } + // 获取返回结果 为customRequest的reslove参数 + // 只有success才会走到这里 + const { ossId, url } = currentFile.response as UploadResult; + currentFile.url = url; + currentFile.uid = ossId; + + const cb = { + type: 'upload', + response: currentFile.response as UploadResult, + } as const; + + currentFile.fileName = transformFilename(cb); + currentFile.name = transformFilename(cb); + currentFile.thumbUrl = transformThumbUrl(cb); + // 标记为上传 watch根据值做处理 + isUpload = true; + // ossID添加 单个文件会被当做string + if (props.maxCount === 1) { + bindValue.value = ossId; + } else { + // 给默认值 + if (!Array.isArray(bindValue.value)) { + bindValue.value = []; + } + // 直接使用.value无法触发useForm的更新(原生是正常的) 需要修改地址 + bindValue.value = [...bindValue.value, ossId]; + } + break; + } + // 上传失败 网络原因导致httpStatus 不等于200 + case 'error': { + removeCurrentFile(currentFile, fileList); + } + } + emit('change', info); + } + + function handleRemove(currentFile: UploadFile) { + function remove() { + // fileList会自行处理删除 这里只需要处理ossId + if (props.maxCount === 1) { + bindValue.value = ''; + } else { + (bindValue.value as string[]).splice( + bindValue.value.indexOf(currentFile.uid), + 1, + ); + } + // 触发remove事件 + emit('remove', currentFile); + } + + if (!props.removeConfirm) { + remove(); + return true; + } + + return new Promise((resolve) => { + Modal.confirm({ + title: $t('pages.common.tip'), + content: $t('component.upload.confirmDelete', [currentFile.name]), + okButtonProps: { danger: true }, + centered: true, + onOk() { + resolve(true); + remove(); + }, + onCancel() { + resolve(false); + }, + }); + }); + } + + /** + * 上传前检测文件大小 + * 拖拽时候前置会有浏览器自身的accept校验 校验失败不会执行此方法 + * @param file file + * @returns file | false + */ + function beforeUpload(file: FileType) { + const isLtMax = file.size / 1024 / 1024 < props.maxSize!; + if (!isLtMax) { + message.error($t('component.upload.maxSize', [props.maxSize])); + return false; + } + // 大坑 Safari不支持file-type库 去除文件类型的校验 + return file; + } + + const uploadAbort = new AbortController(); + /** + * 自定义上传实现 + * @param info + */ + async function customRequest(info: UploadRequestOption) { + const { api } = props; + if (!isFunction(api)) { + console.warn('upload api must exist and be a function'); + return; + } + try { + // 进度条事件 + const progressEvent: AxiosProgressEvent = (e) => { + const percent = Math.trunc((e.loaded / e.total!) * 100); + info.onProgress!({ percent }); + }; + const res = await api(info.file as File, { + onUploadProgress: progressEvent, + signal: uploadAbort.signal, + otherData: props?.data, + }); + info.onSuccess!(res); + if (props.showSuccessMsg) { + message.success($t('component.upload.uploadSuccess')); + } + emit('success', info.file as RcFile, res); + } catch (error: any) { + console.error(error); + info.onError!(error); + } + } + + onUnmounted(() => { + props.abortOnUnmounted && uploadAbort.abort(); + }); + + /** + * 这里默认只监听list地址变化 即重新赋值才会触发watch + * immediate用于初始化触发 + */ + watch( + () => bindValue.value, + async (value) => { + if (value.length === 0) { + // 清空绑定值时,同时清空innerFileList,避免外部使用时还能读取到 + innerFileList.value = []; + return; + } + + // 上传完毕 不需要调用获取信息接口 + if (isUpload) { + // 清理 使下一次状态可用 + isUpload = false; + return; + } + + const resp = await ossInfo(value); + function transformFile(info: OssFile) { + const cb = { type: 'info', response: info } as const; + + const fileitem: UploadFile = { + uid: info.ossId, + name: transformFilename(cb), + fileName: transformFilename(cb), + url: info.url, + thumbUrl: transformThumbUrl(cb), + status: 'done', + }; + return fileitem; + } + const transformOptions = resp.map((item) => transformFile(item)); + innerFileList.value = transformOptions; + // 单文件 丢弃策略 + if (props.maxCount === 1 && resp.length === 0 && !props.keepMissingId) { + bindValue.value = ''; + return; + } + // 多文件 + // 单文件查到了也会走这里的逻辑 filter会报错 需要maxCount判断处理 + if ( + resp.length !== value.length && + !props.keepMissingId && + props.maxCount !== 1 + ) { + // 给默认值 + if (!Array.isArray(bindValue.value)) { + bindValue.value = []; + } + bindValue.value = bindValue.value.filter((ossId) => + resp.map((res) => res.ossId).includes(ossId), + ); + } + }, + { immediate: true }, + ); + + return { + handleChange, + handleRemove, + beforeUpload, + customRequest, + innerFileList, + acceptStr, + }; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/image-upload.vue b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/image-upload.vue new file mode 100644 index 00000000..56584ae4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/image-upload.vue @@ -0,0 +1,190 @@ + + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/note.md b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/note.md new file mode 100644 index 00000000..261cc450 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/note.md @@ -0,0 +1,26 @@ +Safari在执行到beforeUpload方法 + +有两种情况 + +1. 不继续执行 也无法上传(没有调用上传) +2. 报错 + +Unhandled Promise Rejection: TypeError: ReadableStreamBYOBReader needs a ReadableByteStreamController + +https://github.com/oven-sh/bun/issues/12908#issuecomment-2490151231 + +刚开始以为是异步的问题 由于`file-type`调用了异步方法 调试也是在这里没有后续打印了 + +使用别的异步代码测试结果是正常上传的 + +```js +return new Promise((resolve) => + setTimeout(() => resolve(file), 2000), +); +``` + +根本原因在于`file-typ`库的`fileTypeFromBlob`方法不支持Safari 去掉可以正常上传 + +safari不支持`ReadableStreamBYOBReader`api + +详见: https://github.com/sindresorhus/file-type/issues/690 diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/props.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/props.d.ts new file mode 100644 index 00000000..3aa324c1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/components/upload/src/props.d.ts @@ -0,0 +1,122 @@ +import type { UploadFile } from 'ant-design-vue'; +import type { RcFile } from 'ant-design-vue/es/vc-upload/interface'; + +import type { UploadApi, UploadResult } from '#/api'; +import type { OssFile } from '#/api/system/oss/model'; + +import { UploadChangeParam } from 'ant-design-vue'; + +export type UploadType = 'file' | 'image'; + +/** + * 自定义返回文件名/缩略图使用 泛型控制返回是否必填 + * type 为不同的接口返回值 需要自行if判断 + */ +export type CustomGetter = ( + cb: + | { response: OssFile; type: 'info' } + | { response: UploadResult; type: 'upload' }, +) => T extends undefined ? string | undefined : string; + +export interface BaseUploadProps { + /** + * 上传接口 + */ + api?: UploadApi; + /** + * 文件上传失败 是否从展示列表中删除 + * @default true + */ + removeOnError?: boolean; + /** + * 上传成功 是否展示提示信息 + * @default true + */ + showSuccessMsg?: boolean; + /** + * 删除文件前是否需要确认 + * @default false + */ + removeConfirm?: boolean; + /** + * 同antdv参数 + */ + accept?: string; + /** + * 你可能使用的是application/pdf这种mime类型, 但是这样用户可能看不懂, 在这里自定义逻辑 + * @default 原始accept + */ + acceptFormat?: ((accept: string) => string) | string; + /** + * 附带的请求参数 + */ + data?: any; + /** + * 最大上传图片数量 + * maxCount为1时 会被绑定为string而非string[] + * @default 1 + */ + maxCount?: number; + /** + * 文件最大 单位M + * @default 5 + */ + maxSize?: number; + /** + * 是否禁用 + * @default false + */ + disabled?: boolean; + /** + * 是否显示文案 请上传不超过... + * @default true + */ + helpMessage?: boolean; + /** + * 是否支持多选文件,ie10+ 支持。开启后按住 ctrl 可选择多个文件。 + * @default false + */ + multiple?: boolean; + /** + * 是否支持上传文件夹 + * @default false + */ + directory?: boolean; + /** + * 是否支持拖拽上传 + * @default false + */ + enableDragUpload?: boolean; + /** + * 当ossId查询不到文件信息时 比如被删除了 + * 是否保留列表对应的ossId 默认不保留 + * @default false + */ + keepMissingId?: boolean; + /** + * 自定义文件/图片预览逻辑 比如: 你可以改为下载 + * 图片上传默认为预览 + * 文件上传默认为window.open + * @param file file + */ + preview?: (file: UploadFile) => Promise | void; + /** + * 是否在组件Unmounted时取消上传 + * @default true + */ + abortOnUnmounted?: boolean; + /** + * 自定义文件名 需要区分两个接口的返回值 + */ + customFilename?: CustomGetter; + /** + * 自定义缩略图 需要区分两个接口的返回值 + */ + customThumbUrl?: CustomGetter; +} + +export interface UploadEmits { + (e: 'success', file: RcFile, response: UploadResult): void; + (e: 'remove', file: UploadFile): void; + (e: 'change', info: UploadChangeParam): void; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/layouts/auth.vue b/Yi.Vben5.Vue3/apps/web-antd/src/layouts/auth.vue new file mode 100644 index 00000000..18d415bc --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/layouts/auth.vue @@ -0,0 +1,23 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/layouts/basic.vue b/Yi.Vben5.Vue3/apps/web-antd/src/layouts/basic.vue new file mode 100644 index 00000000..2a10d7d7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/layouts/basic.vue @@ -0,0 +1,168 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/layouts/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/layouts/index.ts new file mode 100644 index 00000000..a4320780 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/layouts/index.ts @@ -0,0 +1,6 @@ +const BasicLayout = () => import('./basic.vue'); +const AuthPageLayout = () => import('./auth.vue'); + +const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); + +export { AuthPageLayout, BasicLayout, IFrameView }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/README.md b/Yi.Vben5.Vue3/apps/web-antd/src/locales/README.md new file mode 100644 index 00000000..7b451032 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/README.md @@ -0,0 +1,3 @@ +# locale + +每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/locales/index.ts new file mode 100644 index 00000000..5c0d6500 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/index.ts @@ -0,0 +1,103 @@ +import type { Locale } from 'ant-design-vue/es/locale'; + +import type { App } from 'vue'; + +import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; + +import { ref } from 'vue'; + +import { + $t, + setupI18n as coreSetup, + loadLocalesMapFromDir, +} from '@vben/locales'; +import { preferences } from '@vben/preferences'; + +import antdEnLocale from 'ant-design-vue/es/locale/en_US'; +import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN'; +import dayjs from 'dayjs'; + +const antdLocale = ref(antdDefaultLocale); + +const modules = import.meta.glob('./langs/**/*.json'); + +const localesMap = loadLocalesMapFromDir( + /\.\/langs\/([^/]+)\/(.*)\.json$/, + modules, +); +/** + * 加载应用特有的语言包 + * 这里也可以改造为从服务端获取翻译数据 + * @param lang + */ +async function loadMessages(lang: SupportedLanguagesType) { + const [appLocaleMessages] = await Promise.all([ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + localesMap[lang]!(), + loadThirdPartyMessage(lang), + ]); + return appLocaleMessages.default; +} + +/** + * 加载第三方组件库的语言包 + * @param lang + */ +async function loadThirdPartyMessage(lang: SupportedLanguagesType) { + await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]); +} + +/** + * 加载dayjs的语言包 + * @param lang + */ +async function loadDayjsLocale(lang: SupportedLanguagesType) { + let locale; + switch (lang) { + case 'en-US': { + locale = await import('dayjs/locale/en'); + break; + } + case 'zh-CN': { + locale = await import('dayjs/locale/zh-cn'); + break; + } + // 默认使用英语 + default: { + locale = await import('dayjs/locale/en'); + } + } + if (locale) { + dayjs.locale(locale); + } else { + console.error(`Failed to load dayjs locale for ${lang}`); + } +} + +/** + * 加载antd的语言包 + * @param lang + */ +async function loadAntdLocale(lang: SupportedLanguagesType) { + switch (lang) { + case 'en-US': { + antdLocale.value = antdEnLocale; + break; + } + case 'zh-CN': { + antdLocale.value = antdDefaultLocale; + break; + } + } +} + +async function setupI18n(app: App, options: LocaleSetupOptions = {}) { + await coreSetup(app, { + defaultLocale: preferences.app.locale, + loadMessages, + missingWarn: !import.meta.env.PROD, + ...options, + }); +} + +export { $t, antdLocale, setupI18n }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/component.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/component.json new file mode 100644 index 00000000..d4d9e469 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/component.json @@ -0,0 +1,59 @@ +{ + "cropper": { + "selectImage": "Select Image", + "uploadSuccess": "Uploaded success!", + "imageTooBig": "Image too big", + "modalTitle": "Avatar upload", + "okText": "Confirm and upload", + "btn_reset": "Reset", + "btn_rotate_left": "Counterclockwise rotation", + "btn_rotate_right": "Clockwise rotation", + "btn_scale_x": "Flip horizontal", + "btn_scale_y": "Flip vertical", + "btn_zoom_in": "Zoom in", + "btn_zoom_out": "Zoom out", + "preview": "Preview" + }, + "tenantToggle": { + "placeholder": "Please select a tenant", + "switch": "Switch to tenant: ", + "reset": "Reset to default tenant" + }, + "notice": { + "title": "Notice", + "received": "You have received a new message" + }, + "upload": { + "save": "Save", + "upload": "Upload", + "imgUpload": "ImageUpload", + "uploaded": "Uploaded", + "operating": "Operating", + "del": "Delete", + "download": "download", + "saveWarn": "Please wait for the file to upload and save!", + "saveError": "There is no file successfully uploaded and cannot be saved!", + "preview": "Preview", + "choose": "Select the file", + "accept": "Support {0} format", + "acceptUpload": "Only upload files in {0} format", + "maxSize": "A single file does not exceed {0}MB ", + "maxSizeMultiple": "Only upload files up to {0}MB!", + "maxNumber": "Only upload up to {0} files", + "legend": "Legend", + "fileName": "File name", + "fileSize": "File size", + "fileStatue": "File status", + "pending": "Pending", + "startUpload": "Start upload", + "uploadSuccess": "Upload successfully", + "uploadError": "Upload failed", + "uploading": "Uploading", + "uploadWait": "Please wait for the file upload to finish", + "reUploadFailed": "Re-upload failed files", + "uploadHelpMessage": "Please upload a file in {ext} format that does not exceed {size} .", + "unknownFileType": "Unknown file type, unable to upload", + "confirmDelete": "Confirm file deletion {0}?", + "clickOrDrag": "Click or drag file to this area to upload" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/demos.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/demos.json new file mode 100644 index 00000000..07156434 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/demos.json @@ -0,0 +1,12 @@ +{ + "title": "Demos", + "antd": "Ant Design Vue", + "vben": { + "title": "Project", + "about": "About", + "document": "Document", + "antdv": "Ant Design Vue Version", + "naive-ui": "Naive UI Version", + "element-plus": "Element Plus Version" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/http.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/http.json new file mode 100644 index 00000000..c3ef2a05 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/http.json @@ -0,0 +1,7 @@ +{ + "apiRequestFailed": "Operation failed", + "operationSuccess": "Operation Success", + "successTip": "Success Tip", + "errorTip": "Error Tip", + "loginTimeout": "Login timeout, please log in again" +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/menu.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/menu.json new file mode 100644 index 00000000..ac93f715 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/menu.json @@ -0,0 +1,55 @@ +{ + "root": "Root", + "system": { + "root": "System", + "user": "User", + "role": "Role", + "menu": "Menu", + "dept": "Department", + "post": "Post", + "dict": "Dictionary", + "config": "Parameter Settings", + "notice": "Notifications", + "log": { + "root": "Log", + "operation": "Operation Log", + "login": "Login Log" + }, + "oss": "File", + "client": "Client" + }, + "tenant": { + "root": "Tenant", + "package": "Package" + }, + "monitor": { + "root": "System Monitoring", + "online": "Online Users", + "cache": "Cache Monitoring", + "admin": "Admin Monitoring", + "job": "Task Scheduling Center" + }, + "tool": { + "root": "System Tools", + "gen": "Code Generation" + }, + "workflow": { + "root": "Workflow", + "category": "Process Category", + "model": "Model", + "define": "Process Definition", + "monitor": { + "root": "Process Monitoring", + "instance": "Process Instance", + "todo": "Pending Tasks" + }, + "form": "Form" + }, + "task": { + "root": "My Tasks", + "apply": "My Initiated Tasks", + "todo": "My Pending Tasks", + "done": "My Completed Tasks", + "cc": "My CC" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/page.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/page.json new file mode 100644 index 00000000..8fe0f0ca --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/page.json @@ -0,0 +1,15 @@ +{ + "auth": { + "login": "Login", + "register": "Register", + "codeLogin": "Code Login", + "qrcodeLogin": "Qr Code Login", + "forgetPassword": "Forget Password", + "oauthLogin": "Oauth Login" + }, + "dashboard": { + "title": "Dashboard", + "analytics": "Analytics", + "workspace": "Workspace" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/pages.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/pages.json new file mode 100644 index 00000000..901a75a1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/en-US/pages.json @@ -0,0 +1,27 @@ +{ + "common": { + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "more": "More", + "search": "Search", + "reset": "Reset", + "import": "Import", + "export": "Export", + "expand": "Expand", + "collapse": "Collapse", + "info": "Info", + "clear": "Clear", + "unlock": "Unlock", + "download": "Download", + "sync": "Sync", + "refresh": "Refresh", + "generate": "Generate", + "downloadLoading": "Downloading... Please wait.", + "preview": "Preview", + "tip": "Tip", + "enable": "On", + "disable": "Off", + "beforeCloseTip": "You have unsaved changes. Are you sure you want to exit?" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/component.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/component.json new file mode 100644 index 00000000..3b7eb367 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/component.json @@ -0,0 +1,59 @@ +{ + "cropper": { + "selectImage": "选择图片", + "uploadSuccess": "上传成功", + "imageTooBig": "图片超限", + "modalTitle": "头像上传", + "okText": "确认并上传", + "btn_reset": "重置", + "btn_rotate_left": "逆时针旋转", + "btn_rotate_right": "顺时针旋转", + "btn_scale_x": "水平翻转", + "btn_scale_y": "垂直翻转", + "btn_zoom_in": "放大", + "btn_zoom_out": "缩小", + "preview": "预览" + }, + "tenantToggle": { + "placeholder": "选择租户", + "switch": "切换当前租户为: ", + "reset": "还原为默认租户" + }, + "notice": { + "title": "消息", + "received": "收到新消息" + }, + "upload": { + "save": "保存", + "upload": "上传", + "imgUpload": "图片上传", + "uploaded": "已上传", + "operating": "操作", + "del": "删除", + "download": "下载", + "saveWarn": "请等待文件上传后,保存!", + "saveError": "没有上传成功的文件,无法保存!", + "preview": "预览", + "choose": "选择文件", + "accept": "支持{0}格式", + "acceptUpload": "只能上传{0}格式文件", + "maxSize": "单个文件不超过{0}MB", + "maxSizeMultiple": "只能上传不超过{0}MB的文件!", + "maxNumber": "最多只能上传{0}个文件", + "legend": "略缩图", + "fileName": "文件名", + "fileSize": "文件大小", + "fileStatue": "状态", + "pending": "待上传", + "startUpload": "开始上传", + "uploadSuccess": "上传成功", + "uploadError": "上传失败", + "uploading": "上传中", + "uploadWait": "请等待文件上传结束后操作", + "reUploadFailed": "重新上传失败文件", + "uploadHelpMessage": "请上传不超过{size}的{ext}格式文件", + "unknownFileType": "未知的文件类型, 无法上传", + "confirmDelete": "确认删除文件 {0}?", + "clickOrDrag": "点击或拖动文件到这个区域上传" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/demos.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/demos.json new file mode 100644 index 00000000..93ee722f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/demos.json @@ -0,0 +1,12 @@ +{ + "title": "演示", + "antd": "Ant Design Vue", + "vben": { + "title": "项目", + "about": "关于", + "document": "文档", + "antdv": "Ant Design Vue 版本", + "naive-ui": "Naive UI 版本", + "element-plus": "Element Plus 版本" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/http.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/http.json new file mode 100644 index 00000000..1df058fa --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/http.json @@ -0,0 +1,7 @@ +{ + "apiRequestFailed": "请求出错,请稍候重试", + "operationSuccess": "操作成功", + "successTip": "成功提示", + "errorTip": "错误提示", + "loginTimeout": "登录超时, 请重新登录" +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/menu.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/menu.json new file mode 100644 index 00000000..6772532b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/menu.json @@ -0,0 +1,55 @@ +{ + "root": "根目录", + "system": { + "root": "系统管理", + "user": "用户管理", + "role": "角色管理", + "menu": "菜单管理", + "dept": "部门管理", + "post": "岗位管理", + "dict": "字典管理", + "config": "参数设置", + "notice": "通知公告", + "log": { + "root": "日志管理", + "operation": "操作日志", + "login": "登录日志" + }, + "oss": "文件管理", + "client": "客户端管理" + }, + "tenant": { + "root": "租户管理", + "package": "套餐管理" + }, + "monitor": { + "root": "系统监控", + "online": "在线用户", + "cache": "缓存监控", + "admin": "Admin监控", + "job": "任务调度中心" + }, + "tool": { + "root": "系统工具", + "gen": "代码生成" + }, + "workflow": { + "root": "工作流", + "category": "流程分类", + "model": "模型管理", + "define": "流程定义", + "monitor": { + "root": "流程监控", + "instance": "流程实例", + "todo": "待办任务" + }, + "form": "表单管理" + }, + "task": { + "root": "我的任务", + "apply": "我发起的", + "todo": "我的待办", + "done": "我的已办", + "cc": "我的抄送" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/page.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/page.json new file mode 100644 index 00000000..77dceffa --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/page.json @@ -0,0 +1,15 @@ +{ + "auth": { + "login": "登录", + "register": "注册", + "codeLogin": "验证码登录", + "qrcodeLogin": "二维码登录", + "forgetPassword": "忘记密码", + "oauthLogin": "第三方登录" + }, + "dashboard": { + "title": "概览", + "analytics": "分析页", + "workspace": "工作台" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/pages.json b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/pages.json new file mode 100644 index 00000000..eab87798 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/locales/langs/zh-CN/pages.json @@ -0,0 +1,27 @@ +{ + "common": { + "add": "新增", + "edit": "编辑", + "delete": "删除", + "more": "更多", + "search": "搜索", + "reset": "重置", + "import": "导入", + "export": "导出", + "expand": "展开", + "collapse": "收起", + "info": "详情", + "clear": "清空", + "unlock": "解锁", + "download": "下载", + "sync": "同步", + "refresh": "刷新", + "generate": "生成", + "downloadLoading": "下载中, 请稍后...", + "preview": "预览", + "tip": "提示", + "enable": "启用", + "disable": "禁用", + "beforeCloseTip": "您有未保存的更改,确认要退出吗?" + } +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/main.ts b/Yi.Vben5.Vue3/apps/web-antd/src/main.ts new file mode 100644 index 00000000..5d728a02 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/main.ts @@ -0,0 +1,31 @@ +import { initPreferences } from '@vben/preferences'; +import { unmountGlobalLoading } from '@vben/utils'; + +import { overridesPreferences } from './preferences'; + +/** + * 应用初始化完成之后再进行页面加载渲染 + */ +async function initApplication() { + // name用于指定项目唯一标识 + // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 + const env = import.meta.env.PROD ? 'prod' : 'dev'; + const appVersion = import.meta.env.VITE_APP_VERSION; + const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; + + // app偏好设置初始化 + await initPreferences({ + namespace, + overrides: overridesPreferences, + }); + + // 启动应用并挂载 + // vue应用主要逻辑及视图 + const { bootstrap } = await import('./bootstrap'); + await bootstrap(namespace); + + // 移除并销毁loading + unmountGlobalLoading(); +} + +initApplication(); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/preferences.ts b/Yi.Vben5.Vue3/apps/web-antd/src/preferences.ts new file mode 100644 index 00000000..1e7f1a04 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/preferences.ts @@ -0,0 +1,72 @@ +import { defineOverridesPreferences } from '@vben/preferences'; + +/** + * @description 项目配置文件 + * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 + * !!! 更改配置后请清空缓存,否则可能不生效 + */ +export const overridesPreferences = defineOverridesPreferences({ + // overrides + app: { + /** + * 不要动这里 后端路由模式 + */ + accessMode: 'backend', + /** + * 不需要refresh token 由后端处理 + */ + enableRefreshToken: false, + /** + * 这里可以设置默认头像 url链接或vite导入的图片链接 + */ + // defaultAvatar: '', + /** + * 在这里设置应用标题 + */ + name: import.meta.env.VITE_APP_TITLE, + /** + * 不支持modal模式 需要改动的地方太多 + * 1. 正常重新登录后不会再触发接口请求 即触发登录超时的页面为空数据 + * 2. 切换租户登录后不会重新加载菜单 + */ + // loginExpiredMode: 'modal', + }, + footer: { + /** + * 不显示footer + */ + enable: false, + }, + tabbar: { + /** + * 标签tab 持久化 关闭 + */ + persist: false, + // styleType: 'card', + }, + theme: { + /** + * 浅色sidebar + */ + semiDarkSidebar: false, + /** + * 圆角大小 换算比例为1.6px = 0.1radius + * 这里为6px 与antd保持一致 + */ + radius: '0.375', + }, + /** + * !!! 更改配置后请清空浏览器缓存 + * 在这里更换logo + * source可选值: + * 1. 本地public目录下的图片 需要加上/ 比如:/logo.png + * 2. 网络图片链接 + * 3. vite导入的图片 import xxx from 'xxx.png' + * + * !!! 更改配置后请清空浏览器缓存 + */ + // logo: { + // enable: true, + // source: '', + // }, +}); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/access.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/access.ts new file mode 100644 index 00000000..5d2bd4b4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/access.ts @@ -0,0 +1,266 @@ +import type { + ComponentRecordType, + GenerateMenuAndRoutesOptions, + RouteMeta, + RouteRecordStringComponent, +} from '@vben/types'; + +import type { Menu } from '#/api'; + +import { generateAccessible } from '@vben/access'; +import { preferences } from '@vben/preferences'; + +import { message } from 'ant-design-vue'; +import { cloneDeep } from 'lodash-es'; + +import { getAllMenusApi } from '#/api'; +import { BasicLayout, IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +import { localMenuList } from './routes/local'; + +const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); +const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue'); + +/** + * 后端返回的meta有时候不包括需要的信息 比如activePath等 + * 在这里定义映射 + */ +const routeMetaMapping: Record> = { + '/system/role-auth/user/:roleId': { + activePath: '/system/role', + requireHomeRedirect: true, + }, + + '/system/oss-config/index': { + activePath: '/system/oss', + requireHomeRedirect: true, + }, + + '/tool/gen-edit/index/:tableId': { + activePath: '/tool/gen', + requireHomeRedirect: true, + }, + + '/workflow/design/index': { + activePath: '/workflow/processDefinition', + requireHomeRedirect: true, + }, + + '/workflow/leaveEdit/index': { + activePath: '/demo/leave', + requireHomeRedirect: true, + }, +}; + +/** + * 后台路由转vben路由 + * @param menuList 后台菜单 + * @param parentPath 上级目录 + * @returns vben路由 + */ +function backMenuToVbenMenu( + menuList: Menu[], + parentPath = '', +): RouteRecordStringComponent[] { + const resultList: RouteRecordStringComponent[] = []; + menuList.forEach((menu) => { + // 根目录为菜单形式 + // 固定有一个children children为当前菜单 + if (menu.path === '/' && menu.children && menu.children.length === 1) { + if (!menu.children || !menu.children[0]) { + return; + } + + // 需要处理根目录为内嵌的情况 不会带InnerLink + if (/^https?:\/\//.test(menu.children[0].path)) { + menu.children[0].component = 'InnerLink'; + menu.children[0].path = menu.children[0].path + .replaceAll(/^https?:\/\//g, '') + .replaceAll('/#/', '') + .replaceAll('#', '') + .replaceAll(/[?&]/g, ''); + } + + // 取子路径作为父级路径 + const path = menu.children[0].path; + // 取子菜单的meta作为当前菜单的meta + menu.meta = menu.children[0].meta; + // 由于在一级路由 父级路径需要加上/ + menu.path = `/${path}`; + menu.component = 'RootMenu'; + // 将子路径设置为'' + menu.children[0].path = ''; + } + + // 外链: http开头 & 组件为Layout || ParentView + // 正则判断是否为http://或者https://开头 + if ( + /^https?:\/\//.test(menu.path) && + (menu.component === 'Layout' || menu.component === 'ParentView') + ) { + menu.component = 'Link'; + } + + // 内嵌iframe 组件为InnerLink + if (menu.meta?.link && menu.component === 'InnerLink') { + menu.component = 'IFrameView'; + } + + /** + * 拼接path + * menu.path为''(根目录路由) 则不拼接 + */ + if (parentPath && menu.path) { + menu.path = `${parentPath}/${menu.path}`; + } + + // 创建vben路由对象 + const vbenRoute: RouteRecordStringComponent = { + component: menu.component, + meta: { + // 当前路由不在菜单显示 但是可以通过链接访问 + // 不可访问的路由由后端控制隐藏(不返回对应路由) + hideInMenu: menu.hidden, + icon: menu.meta?.icon, + keepAlive: !menu.meta?.noCache, + title: menu.meta?.title, + }, + name: menu.name, + path: menu.path, + }; + + // 处理meta映射 + if (Object.keys(routeMetaMapping).includes(vbenRoute.path)) { + const routeMeta = routeMetaMapping[vbenRoute.path]; + if (routeMeta) { + vbenRoute.meta = { + ...vbenRoute.meta, + ...(routeMeta as RouteMeta), + }; + } + } + + // 添加路由参数信息 + if (menu.query) { + try { + const query = JSON.parse(menu.query); + vbenRoute.meta && (vbenRoute.meta.query = query); + } catch { + console.error('错误的路由参数类型, 必须为[json]格式'); + } + } + + /** + * 处理不同组件 + */ + switch (menu.component) { + /** + * iframe内嵌 + */ + case 'IFrameView': { + vbenRoute.component = 'IFrameView'; + if (vbenRoute.meta) { + vbenRoute.meta.iframeSrc = menu.meta.link; + } + /** + * 需要判断特殊情况 比如vue的hash是带#的 + * 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb + * 比如链接 aaa.com/?bbb=xxx + * 需要去除# 否则无法被添加到路由 + */ + vbenRoute.path = vbenRoute.path + // 替换https:// 或者 http:// + .replaceAll(/^https?:\/\//g, '') + .replaceAll('/#/', '') + .replaceAll('#', '') + .replaceAll(/[?&]/g, ''); + break; + } + case 'Layout': { + vbenRoute.component = 'BasicLayout'; + break; + } + /** + * 外链 新窗口打开 + */ + case 'Link': { + if (vbenRoute.meta) { + vbenRoute.meta.link = menu.meta.link; + } + vbenRoute.component = 'BasicLayout'; + break; + } + /** + * 三级以上菜单 父级component为ParentView + * 不能为layout 会套两层BasicLayout + */ + case 'ParentView': { + vbenRoute.component = ''; + break; + } + /** + * 根目录菜单 + */ + case 'RootMenu': { + if (vbenRoute.meta) { + vbenRoute.meta.hideChildrenInMenu = true; + } + vbenRoute.component = 'BasicLayout'; + break; + } + /** + * 其他自定义组件 如system/user/index 拼接/ + */ + default: { + vbenRoute.component = `/${menu.component}`; + break; + } + } + + // children处理 + if (menu.children && menu.children.length > 0) { + vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path); + } + // 添加 + resultList.push(vbenRoute); + }); + return resultList; +} + +async function generateAccess(options: GenerateMenuAndRoutesOptions) { + const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); + + const layoutMap: ComponentRecordType = { + BasicLayout, + IFrameView, + NotFoundComponent, + }; + + return await generateAccessible(preferences.app.accessMode, { + ...options, + fetchMenuListAsync: async () => { + // 清除以前的message + message.destroy(); + message.loading({ + content: `${$t('common.loadingMenu')}...`, + duration: 1, + }); + // 后台返回路由/菜单 + const backMenuList = await getAllMenusApi(); + // 转换为vben能用的路由 + const vbenMenuList = backMenuToVbenMenu(backMenuList); + // 特别注意 这里要深拷贝 + const menuList = [...cloneDeep(localMenuList), ...vbenMenuList]; + // console.log('menuList', menuList); + return menuList; + }, + // 可以指定没有权限跳转403页面 + forbiddenComponent, + // 如果 route.meta.menuVisibleWithForbidden = true + layoutMap, + pageMap, + }); +} + +export { generateAccess }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/guard.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/guard.ts new file mode 100644 index 00000000..a1ad6d88 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/guard.ts @@ -0,0 +1,133 @@ +import type { Router } from 'vue-router'; + +import { LOGIN_PATH } from '@vben/constants'; +import { preferences } from '@vben/preferences'; +import { useAccessStore, useUserStore } from '@vben/stores'; +import { startProgress, stopProgress } from '@vben/utils'; + +import { accessRoutes, coreRouteNames } from '#/router/routes'; +import { useAuthStore } from '#/store'; + +import { generateAccess } from './access'; + +/** + * 通用守卫配置 + * @param router + */ +function setupCommonGuard(router: Router) { + // 记录已经加载的页面 + const loadedPaths = new Set(); + + router.beforeEach((to) => { + to.meta.loaded = loadedPaths.has(to.path); + + // 页面加载进度条 + if (!to.meta.loaded && preferences.transition.progress) { + startProgress(); + } + return true; + }); + + router.afterEach((to) => { + // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 + + loadedPaths.add(to.path); + + // 关闭页面加载进度条 + if (preferences.transition.progress) { + stopProgress(); + } + }); +} + +/** + * 权限访问守卫配置 + * @param router + */ +function setupAccessGuard(router: Router) { + router.beforeEach(async (to, from) => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const authStore = useAuthStore(); + + // 基本路由,这些路由不需要进入权限拦截 + if (coreRouteNames.includes(to.name as string)) { + if (to.path === LOGIN_PATH && accessStore.accessToken) { + return decodeURIComponent( + (to.query?.redirect as string) || + userStore.userInfo?.homePath || + preferences.app.defaultHomePath, + ); + } + return true; + } + + // accessToken 检查 + if (!accessStore.accessToken) { + // 明确声明忽略权限访问权限,则可以访问 + if (to.meta.ignoreAccess) { + return true; + } + + // 没有访问权限,跳转登录页面 + if (to.fullPath !== LOGIN_PATH) { + return { + path: LOGIN_PATH, + // 如不需要,直接删除 query + query: + to.fullPath === preferences.app.defaultHomePath + ? {} + : { redirect: encodeURIComponent(to.fullPath) }, + // 携带当前跳转的页面,登录后重新跳转该页面 + replace: true, + }; + } + return to; + } + + // 是否已经生成过动态路由 + if (accessStore.isAccessChecked) { + return true; + } + + // 生成路由表 + // 当前登录用户拥有的角色标识列表 + const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); + const userRoles = userInfo.roles ?? []; + + // 生成菜单和路由 + const { accessibleMenus, accessibleRoutes } = await generateAccess({ + roles: userRoles, + router, + // 则会在菜单中显示,但是访问会被重定向到403 + routes: accessRoutes, + }); + + // 保存菜单信息和路由信息 + accessStore.setAccessMenus(accessibleMenus); + accessStore.setAccessRoutes(accessibleRoutes); + accessStore.setIsAccessChecked(true); + const redirectPath = (from.query.redirect ?? + (to.path === preferences.app.defaultHomePath + ? userInfo.homePath || preferences.app.defaultHomePath + : to.fullPath)) as string; + + return { + ...router.resolve(decodeURIComponent(redirectPath)), + replace: true, + }; + }); +} + +/** + * 项目守卫配置 + * @param router + */ +function createRouterGuard(router: Router) { + /** 通用 */ + setupCommonGuard(router); + /** 权限访问 */ + setupAccessGuard(router); +} + +export { createRouterGuard }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/index.ts new file mode 100644 index 00000000..48402303 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/index.ts @@ -0,0 +1,37 @@ +import { + createRouter, + createWebHashHistory, + createWebHistory, +} from 'vue-router'; + +import { resetStaticRoutes } from '@vben/utils'; + +import { createRouterGuard } from './guard'; +import { routes } from './routes'; + +/** + * @zh_CN 创建vue-router实例 + */ +const router = createRouter({ + history: + import.meta.env.VITE_ROUTER_HISTORY === 'hash' + ? createWebHashHistory(import.meta.env.VITE_BASE) + : createWebHistory(import.meta.env.VITE_BASE), + // 应该添加到路由的初始路由列表。 + routes, + scrollBehavior: (to, _from, savedPosition) => { + if (savedPosition) { + return savedPosition; + } + return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; + }, + // 是否应该禁止尾部斜杠。 + // strict: true, +}); + +const resetRoutes = () => resetStaticRoutes(router, routes); + +// 创建路由守卫 +createRouterGuard(router); + +export { resetRoutes, router }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/core.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/core.ts new file mode 100644 index 00000000..172f3d18 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/core.ts @@ -0,0 +1,105 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { LOGIN_PATH } from '@vben/constants'; +import { preferences } from '@vben/preferences'; + +import { $t } from '#/locales'; + +const BasicLayout = () => import('#/layouts/basic.vue'); +const AuthPageLayout = () => import('#/layouts/auth.vue'); +/** 全局404页面 */ +const fallbackNotFoundRoute: RouteRecordRaw = { + component: () => import('#/views/_core/fallback/not-found.vue'), + meta: { + hideInBreadcrumb: true, + hideInMenu: true, + hideInTab: true, + title: '404', + }, + name: 'FallbackNotFound', + path: '/:path(.*)*', +}; + +/** 基本路由,这些路由是必须存在的 */ +const coreRoutes: RouteRecordRaw[] = [ + /** + * 根路由 + * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 + * 此路由必须存在,且不应修改 + */ + { + component: BasicLayout, + meta: { + hideInBreadcrumb: true, + title: 'Root', + }, + name: 'Root', + path: '/', + redirect: preferences.app.defaultHomePath, + children: [], + }, + { + component: () => import('#/views/_core/social-callback/index.vue'), + meta: { + title: $t('page.auth.oauthLogin'), + }, + name: 'OAuthRedirect', + path: '/social-callback', + }, + { + component: AuthPageLayout, + meta: { + hideInTab: true, + title: 'Authentication', + }, + name: 'Authentication', + path: '/auth', + redirect: LOGIN_PATH, + children: [ + { + name: 'Login', + path: 'login', + component: () => import('#/views/_core/authentication/login.vue'), + meta: { + title: $t('page.auth.login'), + }, + }, + { + name: 'CodeLogin', + path: 'code-login', + component: () => import('#/views/_core/authentication/code-login.vue'), + meta: { + title: $t('page.auth.codeLogin'), + }, + }, + { + name: 'QrCodeLogin', + path: 'qrcode-login', + component: () => + import('#/views/_core/authentication/qrcode-login.vue'), + meta: { + title: $t('page.auth.qrcodeLogin'), + }, + }, + { + name: 'ForgetPassword', + path: 'forget-password', + component: () => + import('#/views/_core/authentication/forget-password.vue'), + meta: { + title: $t('page.auth.forgetPassword'), + }, + }, + { + name: 'Register', + path: 'register', + component: () => import('#/views/_core/authentication/register.vue'), + meta: { + title: $t('page.auth.register'), + }, + }, + ], + }, +]; + +export { coreRoutes, fallbackNotFoundRoute }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/index.ts new file mode 100644 index 00000000..1a92413f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/index.ts @@ -0,0 +1,41 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; + +import { coreRoutes, fallbackNotFoundRoute } from './core'; +import { workflowIframeRoutes } from './workflow-iframe'; + +const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { + eager: true, +}); + +// 有需要可以自行打开注释,并创建文件夹 +// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); +// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); + +/** 动态路由 */ +const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); + +/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ +// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); +// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); +const staticRoutes: RouteRecordRaw[] = []; +const externalRoutes: RouteRecordRaw[] = []; + +/** 路由列表,由基本路由、外部路由和404兜底路由组成 + * 无需走权限验证(会一直显示在菜单中) */ +const routes: RouteRecordRaw[] = [ + ...coreRoutes, + ...externalRoutes, + ...workflowIframeRoutes, + fallbackNotFoundRoute, +]; + +/** 基本路由(登录, 第三方登录, 注册等) + workflowIframe路由不需要拦截 */ +const basicRoutes = [...coreRoutes, ...workflowIframeRoutes]; +/** 基本路由列表,这些路由不需要进入权限拦截 */ +const coreRouteNames = traverseTreeValues(basicRoutes, (route) => route.name); + +/** 有权限校验的路由列表,包含动态路由和静态路由 */ +const accessRoutes = [...dynamicRoutes, ...staticRoutes]; +export { accessRoutes, coreRouteNames, routes }; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/local.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/local.ts new file mode 100644 index 00000000..6026fb72 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/local.ts @@ -0,0 +1,64 @@ +import type { RouteRecordStringComponent } from '@vben/types'; + +import { $t } from '@vben/locales'; + +const { + version, + // vite inject-metadata 插件注入的全局变量 +} = __VBEN_ADMIN_METADATA__ || {}; + +/** + * 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面 + * 也可以直接在菜单管理配置 + */ +const localRoutes: RouteRecordStringComponent[] = [ + { + component: '/_core/profile/index', + meta: { + icon: 'mingcute:profile-line', + title: $t('ui.widgets.profile'), + hideInMenu: true, + requireHomeRedirect: true, + }, + name: 'Profile', + path: '/profile', + }, +]; + +/** + * 这里放本地路由 + */ +export const localMenuList: RouteRecordStringComponent[] = [ + { + component: 'BasicLayout', + meta: { + order: -1, + title: 'page.dashboard.title', + // 不使用基础布局(仅在顶级生效) + noBasicLayout: true, + }, + name: 'Dashboard', + path: '/', + redirect: '/analytics', + children: [ + { + name: 'Analytics', + path: '/analytics', + component: '/dashboard/analytics/index', + meta: { + affixTab: true, + title: 'page.dashboard.analytics', + }, + }, + { + name: 'Workspace', + path: '/workspace', + component: '/dashboard/workspace/index', + meta: { + title: 'page.dashboard.workspace', + }, + }, + ], + }, + ...localRoutes, +]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/dashboard.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/dashboard.ts new file mode 100644 index 00000000..5254dc65 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/dashboard.ts @@ -0,0 +1,38 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'lucide:layout-dashboard', + order: -1, + title: $t('page.dashboard.title'), + }, + name: 'Dashboard', + path: '/dashboard', + children: [ + { + name: 'Analytics', + path: '/analytics', + component: () => import('#/views/dashboard/analytics/index.vue'), + meta: { + affixTab: true, + icon: 'lucide:area-chart', + title: $t('page.dashboard.analytics'), + }, + }, + { + name: 'Workspace', + path: '/workspace', + component: () => import('#/views/dashboard/workspace/index.vue'), + meta: { + icon: 'carbon:workspace', + title: $t('page.dashboard.workspace'), + }, + }, + ], + }, +]; + +export default routes; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/vben.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/vben.ts new file mode 100644 index 00000000..a54bb8f8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/modules/vben.ts @@ -0,0 +1,90 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { + VBEN_DOC_URL, + VBEN_ELE_PREVIEW_URL, + VBEN_GITHUB_URL, + VBEN_LOGO_URL, + VBEN_NAIVE_PREVIEW_URL, +} from '@vben/constants'; + +import { IFrameView } from '#/layouts'; +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + meta: { + badgeType: 'dot', + icon: VBEN_LOGO_URL, + order: 9998, + title: $t('demos.vben.title'), + }, + name: 'VbenProject', + path: '/vben-admin', + children: [ + { + name: 'VbenAbout', + path: '/vben-admin/about', + component: () => import('#/views/_core/about/index.vue'), + meta: { + icon: 'lucide:copyright|offline', + title: $t('demos.vben.about'), + }, + }, + { + name: 'VbenDocument', + path: '/vben-admin/document', + component: IFrameView, + meta: { + icon: 'lucide:book-open-text|offline', + link: VBEN_DOC_URL, + title: $t('demos.vben.document'), + }, + }, + { + name: 'VbenGithub', + path: '/vben-admin/github', + component: IFrameView, + meta: { + icon: 'mdi:github', + link: VBEN_GITHUB_URL, + title: 'Github', + }, + }, + { + name: 'VbenNaive', + path: '/vben-admin/naive', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: 'logos:naiveui', + link: VBEN_NAIVE_PREVIEW_URL, + title: $t('demos.vben.naive-ui'), + }, + }, + { + name: 'VbenElementPlus', + path: '/vben-admin/ele', + component: IFrameView, + meta: { + badgeType: 'dot', + icon: 'logos:element', + link: VBEN_ELE_PREVIEW_URL, + title: $t('demos.vben.element-plus'), + }, + }, + ], + }, + { + name: 'VbenAbout', + path: '/vben-admin/about', + component: () => import('#/views/_core/about/index.vue'), + meta: { + icon: 'lucide:copyright', + title: $t('demos.vben.about'), + order: 9999, + }, + }, +]; + +export default routes; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/workflow-iframe.ts b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/workflow-iframe.ts new file mode 100644 index 00000000..137088f6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/router/routes/workflow-iframe.ts @@ -0,0 +1,18 @@ +import type { RouteRecordRaw } from '@vben/types'; + +/** + * 该文件存放workflow表单的iframe内嵌路由 + * 不需要权限认证 少走两个接口😅 + */ +export const workflowIframeRoutes: RouteRecordRaw[] = [ + // 这里是iframe使用的 去掉外层的BasicLayout + { + name: 'WorkflowLeaveInner', + path: '/workflow/leaveEdit/index/iframe', + component: () => import('#/views/workflow/leave/leave-form.vue'), + meta: { + hideInTab: true, + title: '请假申请', + }, + }, +]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/store/auth.ts b/Yi.Vben5.Vue3/apps/web-antd/src/store/auth.ts new file mode 100644 index 00000000..e7b736e7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/store/auth.ts @@ -0,0 +1,145 @@ +import type { LoginAndRegisterParams } from '@vben/common-ui'; +import type { UserInfo } from '@vben/types'; + +import { ref } from 'vue'; +import { useRouter } from 'vue-router'; + +import { LOGIN_PATH } from '@vben/constants'; +import { preferences } from '@vben/preferences'; +import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; + +import { notification } from 'ant-design-vue'; +import { defineStore } from 'pinia'; + +import { doLogout, getUserInfoApi, loginApi, seeConnectionClose } from '#/api'; +import { $t } from '#/locales'; + +import { useDictStore } from './dict'; + +export const useAuthStore = defineStore('auth', () => { + const accessStore = useAccessStore(); + const userStore = useUserStore(); + const router = useRouter(); + + const loginLoading = ref(false); + + /** + * 异步处理登录操作 + * Asynchronously handle the login process + * @param params 登录表单数据 + */ + async function authLogin( + params: LoginAndRegisterParams, + onSuccess?: () => Promise | void, + ) { + // 异步处理用户登录操作并获取 accessToken + let userInfo: null | UserInfo = null; + try { + loginLoading.value = true; + const { token, refreshToken } = await loginApi(params); + + // 将 accessToken 存储到 accessStore 中 + accessStore.setAccessToken(token); + accessStore.setRefreshToken(refreshToken); + + // 获取用户信息并存储到 accessStore 中 + userInfo = await fetchUserInfo(); + /** + * 设置用户信息 + */ + userStore.setUserInfo(userInfo); + /** + * 在这里设置权限 + */ + accessStore.setAccessCodes(userInfo.permissions); + + if (accessStore.loginExpired) { + accessStore.setLoginExpired(false); + } else { + onSuccess + ? await onSuccess?.() + : await router.push(preferences.app.defaultHomePath); + } + + if (userInfo?.realName) { + notification.success({ + description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, + duration: 3, + message: $t('authentication.loginSuccess'), + }); + } + } finally { + loginLoading.value = false; + } + + return { + userInfo, + }; + } + + async function logout(redirect: boolean = true) { + try { + await seeConnectionClose(); + await doLogout(); + } catch (error) { + console.error(error); + } finally { + resetAllStores(); + accessStore.setLoginExpired(false); + + // 回登陆页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: redirect + ? { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } + : {}, + }); + } + } + + async function fetchUserInfo() { + const backUserInfo = await getUserInfoApi(); + /** + * 登录超时的情况 + */ + if (!backUserInfo) { + throw new Error('获取用户信息失败.'); + } + const { permissionCodes = [], roleCodes = [], user } = backUserInfo; + /** + * 从后台user -> vben user转换 + */ + const userInfo: UserInfo = { + avatar: user.avatar ?? '', + permissions: permissionCodes, + realName: user.nickName, + roles: roleCodes, + userId: user.userId, + username: user.userName, + email: user.email ?? '', + }; + console.log('userInfo', userInfo); + userStore.setUserInfo(userInfo); + /** + * 需要重新加载字典 + * 比如退出登录切换到其他租户 + */ + const dictStore = useDictStore(); + dictStore.resetCache(); + return userInfo; + } + + function $reset() { + loginLoading.value = false; + } + + return { + $reset, + authLogin, + fetchUserInfo, + loginLoading, + logout, + }; +}); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/store/dict.ts b/Yi.Vben5.Vue3/apps/web-antd/src/store/dict.ts new file mode 100644 index 00000000..c24f4c3c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/store/dict.ts @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import type { DictData } from '#/api/system/dict/dict-data-model'; + +import { reactive } from 'vue'; + +import { defineStore } from 'pinia'; + +/** + * antd使用 select和radio通用 + * 本质上是对DictData的拓展 + */ +export interface DictOption extends DictData { + disabled?: boolean; + label: string; + value: number | string; +} + +/** + * 将字典数据转为Options + * @param data 字典数据 + * @param formatNumber 是否需要将value格式化为number类型 + * @returns options + */ +export function dictToOptions( + data: DictData[], + formatNumber = false, +): DictOption[] { + return data.map((item) => ({ + ...item, + label: item.dictLabel, + value: formatNumber ? Number(item.dictValue) : item.dictValue, + })); +} + +export const useDictStore = defineStore('app-dict', () => { + /** + * select radio checkbox等使用 只能为固定格式{label, value} + */ + const dictOptionsMap = reactive(new Map()); + /** + * 添加一个字典请求状态的缓存 + * + * 主要解决多次请求重复api的问题(不能用abortController 会导致除了第一个其他的获取的全为空) + * 比如在一个页面 index表单 modal drawer总共会请求三次 但是获取的都是一样的数据 + * 相当于加锁 保证只有第一次请求的结果能拿到 + */ + const dictRequestCache = reactive( + new Map>(), + ); + + function getDictOptions(dictName: string): DictOption[] { + if (!dictName) return []; + // 没有key 添加一个空数组 + if (!dictOptionsMap.has(dictName)) { + dictOptionsMap.set(dictName, []); + } + // 这里拿到的就不可能为空了 + return dictOptionsMap.get(dictName)!; + } + + function resetCache() { + dictRequestCache.clear(); + dictOptionsMap.clear(); + /** + * 不需要清空dictRequestCache 每次请求成功/失败都清空key + */ + } + + /** + * 核心逻辑 + * + * 不能直接粗暴使用set 会导致之前return的空数组跟现在的数组指向不是同一个地址 数据也就为空了 + * + * 判断是否已经存在key 并且数组长度为0 说明该次要处理的数据是return的空数组 直接push(不修改指向) + * 否则 直接set + * + */ + function setDictInfo( + dictName: string, + dictValue: DictData[], + formatNumber = false, + ) { + if ( + dictOptionsMap.has(dictName) && + dictOptionsMap.get(dictName)?.length === 0 + ) { + dictOptionsMap + .get(dictName) + ?.push(...dictToOptions(dictValue, formatNumber)); + } else { + dictOptionsMap.set(dictName, dictToOptions(dictValue, formatNumber)); + } + } + + function $reset() { + /** + * doNothing + */ + } + + return { + $reset, + dictOptionsMap, + dictRequestCache, + getDictOptions, + resetCache, + setDictInfo, + }; +}); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/store/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/store/index.ts new file mode 100644 index 00000000..7750334b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/store/index.ts @@ -0,0 +1,2 @@ +export * from './auth'; +export * from './notify'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/store/notify.ts b/Yi.Vben5.Vue3/apps/web-antd/src/store/notify.ts new file mode 100644 index 00000000..4fd0a951 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/store/notify.ts @@ -0,0 +1,149 @@ +import type { NotificationItem } from '@vben/layouts'; + +import { computed, ref, watch } from 'vue'; + +import { useAppConfig } from '@vben/hooks'; +import { SvgMessageUrl } from '@vben/icons'; +import { $t } from '@vben/locales'; +import { useAccessStore, useUserStore } from '@vben/stores'; + +import { useEventSource } from '@vueuse/core'; +import { notification } from 'ant-design-vue'; +import dayjs from 'dayjs'; +import { defineStore } from 'pinia'; + +const { apiURL, clientId, sseEnable } = useAppConfig( + import.meta.env, + import.meta.env.PROD, +); + +export const useNotifyStore = defineStore( + 'app-notify', + () => { + /** + * return才会被持久化 存储全部消息 + */ + const notificationList = ref([]); + + const userStore = useUserStore(); + const userId = computed(() => { + return userStore.userInfo?.userId || '0'; + }); + + const notifications = computed(() => { + return notificationList.value.filter( + (item) => item.userId === userId.value, + ); + }); + + /** + * 开始监听sse消息 + */ + function startListeningMessage() { + /** + * 未开启 不监听 + */ + if (!sseEnable) { + return; + } + const accessStore = useAccessStore(); + const token = accessStore.accessToken; + + const sseAddr = `${apiURL}/resource/sse?clientid=${clientId}&Authorization=Bearer ${token}`; + + const { data } = useEventSource(sseAddr, [], { + autoReconnect: { + delay: 1000, + onFailed() { + console.error('sse重连失败.'); + }, + retries: 3, + }, + }); + + watch(data, (message) => { + if (!message) return; + console.log(`接收到消息: ${message}`); + + notification.success({ + description: message, + duration: 3, + message: $t('component.notice.received'), + }); + + notificationList.value.unshift({ + // avatar: `https://api.multiavatar.com/${random(0, 10_000)}.png`, 随机头像 + avatar: SvgMessageUrl, + date: dayjs().format('YYYY-MM-DD HH:mm:ss'), + isRead: false, + message, + title: $t('component.notice.title'), + userId: userId.value, + }); + + // 需要手动置空 vue3在值相同时不会触发watch + data.value = null; + }); + } + + /** + * 设置全部已读 + */ + function setAllRead() { + notificationList.value + .filter((item) => item.userId === userId.value) + .forEach((item) => { + item.isRead = true; + }); + } + + /** + * 设置单条消息已读 + * @param item 通知 + */ + function setRead(item: NotificationItem) { + !item.isRead && (item.isRead = true); + } + + /** + * 清空全部消息 + */ + function clearAllMessage() { + notificationList.value = notificationList.value.filter( + (item) => item.userId !== userId.value, + ); + } + + /** + * 只需要空实现即可 + * 否则会在退出登录清空所有 + */ + function $reset() { + // notificationList.value = []; + } + /** + * 显示小圆点 + */ + const showDot = computed(() => + notificationList.value + .filter((item) => item.userId === userId.value) + .some((item) => !item.isRead), + ); + + return { + $reset, + clearAllMessage, + notificationList, + notifications, + setAllRead, + setRead, + showDot, + startListeningMessage, + }; + }, + { + persist: { + pick: ['notificationList'], + }, + }, +); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/store/tenant.ts b/Yi.Vben5.Vue3/apps/web-antd/src/store/tenant.ts new file mode 100644 index 00000000..2fb26154 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/store/tenant.ts @@ -0,0 +1,44 @@ +import type { TenantOption } from '#/api/core/auth'; + +import { ref } from 'vue'; + +import { defineStore } from 'pinia'; + +import { tenantList as tenantListApi } from '#/api/core/auth'; + +/** + * 用于超级管理员切换租户 + */ +export const useTenantStore = defineStore('app-tenant', () => { + // 是否已经选中租户 + const checked = ref(false); + // 是否开启租户功能 + const tenantEnable = ref(true); + const tenantList = ref([]); + + // 初始化 获取租户信息 + async function initTenant() { + const { tenantEnabled, voList } = await tenantListApi(); + tenantEnable.value = tenantEnabled; + tenantList.value = voList; + } + + async function setChecked(_checked: boolean) { + checked.value = _checked; + } + + function $reset() { + checked.value = false; + tenantEnable.value = true; + tenantList.value = []; + } + + return { + $reset, + checked, + initTenant, + setChecked, + tenantEnable, + tenantList, + }; +}); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/upload-tip.ts b/Yi.Vben5.Vue3/apps/web-antd/src/upload-tip.ts new file mode 100644 index 00000000..8f769bf2 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/upload-tip.ts @@ -0,0 +1,36 @@ +import { onMounted } from 'vue'; + +import { useLocalStorage } from '@vueuse/core'; +import { Modal } from 'ant-design-vue'; + +export function useUploadTip() { + const readTip = useLocalStorage('__upload_tip_read_5.4.0', false); + onMounted(() => { + if (readTip.value || !import.meta.env.DEV) { + return; + } + const modalInstance = Modal.info({ + title: '提示', + centered: true, + content: + '如果你的版本是从低版本升级到后端>5.4.0, 记得执行升级sql, 否则跳转页面(如oss 代码生成配置)等会404', + okButtonProps: { disabled: true }, + onOk() { + modalInstance.destroy(); + readTip.value = true; + }, + }); + + let time = 3; + const interval = setInterval(() => { + modalInstance.update({ + okText: time === 0 ? '我知道了, 不再弹出' : `${time}秒后关闭`, + okButtonProps: { disabled: time > 0 }, + }); + if (time <= 0) { + clearInterval(interval); + } + time--; + }, 1000); + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/dict.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/dict.ts new file mode 100644 index 00000000..bd147be1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/dict.ts @@ -0,0 +1,82 @@ +import { UnauthorizedException } from '#/api/request'; +import { dictDataInfo } from '#/api/system/dict/dict-data'; +import { useDictStore } from '#/store/dict'; + +/** + * 抽取公共逻辑的基础方法 + * @param dictName 字典名称 + * @param dataGetter 获取字典数据的函数 + * @param formatNumber 是否格式化字典value为number类型 + * @returns 数据 + */ +function fetchAndCacheDictData( + dictName: string, + dataGetter: () => T[], + formatNumber = false, +): T[] { + const { dictRequestCache, setDictInfo } = useDictStore(); + // 有调用方决定如何获取数据 + const dataList = dataGetter(); + + // 检查请求状态缓存 + if (dataList.length === 0 && !dictRequestCache.has(dictName)) { + dictRequestCache.set( + dictName, + dictDataInfo(dictName) + .then((resp) => { + // 缓存到store 这样就不用重复获取了 + // 内部处理了push的逻辑 这里不用push + setDictInfo(dictName, resp, formatNumber); + }) + .catch((error) => { + /** + * 需要判断是否为401抛出的特定异常 401清除缓存 + * 其他error清除缓存会导致无限循环调用字典接口 则不做处理 + */ + if (error instanceof UnauthorizedException) { + // 401时 移除字典缓存 下次登录重新获取 + dictRequestCache.delete(dictName); + } + // 其他不做处理 + }) + .finally(() => { + // 移除请求状态缓存 + /** + * 这里主要判断字典item为空的情况(无奈兼容 不给字典item本来就是错误用法) + * 会导致if一直进入逻辑导致接口无限刷新 + * 在这里dictList为空时 不删除缓存 + */ + if (dataList.length > 0) { + dictRequestCache.delete(dictName); + } + }), + ); + } + return dataList; +} + +/** + * 这里是提供给渲染标签使用的方法 + * @deprecated 使用getDictOptions代替 于下个版本删除 + * @param dictName 字典名称 + * @returns 字典信息 + */ +export function getDict(dictName: string) { + const { getDictOptions } = useDictStore(); + return fetchAndCacheDictData(dictName, () => getDictOptions(dictName)); +} + +/** + * 一般是Select, Radio, Checkbox等组件使用 + * @param dictName 字典名称 + * @param formatNumber 是否格式化字典value为number类型 + * @returns Options数组 + */ +export function getDictOptions(dictName: string, formatNumber = false) { + const { getDictOptions } = useDictStore(); + return fetchAndCacheDictData( + dictName, + () => getDictOptions(dictName), + formatNumber, + ); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/crypto.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/crypto.ts new file mode 100644 index 00000000..34696d1a --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/crypto.ts @@ -0,0 +1,80 @@ +import CryptoJS from 'crypto-js'; + +function randomUUID() { + const chars = [ + ...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + ]; + const uuid = Array.from({ length: 36 }); + let rnd = 0; + let r: number; + for (let i = 0; i < 36; i++) { + if (i === 8 || i === 13 || i === 18 || i === 23) { + uuid[i] = '-'; + } else if (i === 14) { + uuid[i] = '4'; + } else { + if (rnd <= 0x02) + rnd = Math.trunc(0x2_00_00_00 + Math.random() * 0x1_00_00_00); + r = rnd & 16; + rnd = rnd >> 4; + uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]; + } + } + return uuid.join('').replaceAll('-', '').toLowerCase(); +} + +/** + * 随机生成aes 密钥 + * + * @returns aes 密钥 + */ +export function generateAesKey() { + return CryptoJS.enc.Utf8.parse(randomUUID()); +} + +/** + * base64编码 + * @param str + * @returns base64编码 + */ +export function encryptBase64(str: CryptoJS.lib.WordArray) { + return CryptoJS.enc.Base64.stringify(str); +} + +/** + * 使用公钥加密 + * @param message 加密内容 + * @param aesKey aesKey + * @returns 使用公钥加密 + */ +export function encryptWithAes( + message: string, + aesKey: CryptoJS.lib.WordArray, +) { + const encrypted = CryptoJS.AES.encrypt(message, aesKey, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }); + return encrypted.toString(); +} + +/** + * 解密base64 + */ +export function decryptBase64(str: string) { + return CryptoJS.enc.Base64.parse(str); +} + +/** + * 使用密钥对数据进行解密 + */ +export function decryptWithAes( + message: string, + aesKey: CryptoJS.lib.WordArray, +) { + const decrypted = CryptoJS.AES.decrypt(message, aesKey, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }); + return decrypted.toString(CryptoJS.enc.Utf8); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/jsencrypt.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/jsencrypt.ts new file mode 100644 index 00000000..2e1e6a6b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/encryption/jsencrypt.ts @@ -0,0 +1,31 @@ +// 密钥对生成 http://web.chacuo.net/netrsakeypair +import { useAppConfig } from '@vben/hooks'; + +import JSEncrypt from 'jsencrypt'; + +const { rsaPrivateKey, rsaPublicKey } = useAppConfig( + import.meta.env, + import.meta.env.PROD, +); + +/** + * 加密 + * @param txt 需要加密的数据 + * @returns 加密后的数据 + */ +export function encrypt(txt: string) { + const instance = new JSEncrypt(); + instance.setPublicKey(rsaPublicKey); + return instance.encrypt(txt); +} + +/** + * 解密 + * @param txt 需要解密的数据 + * @returns 解密后的数据 + */ +export function decrypt(txt: string) { + const instance = new JSEncrypt(); + instance.setPrivateKey(rsaPrivateKey); + return instance.decrypt(txt); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/base64Conver.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/base64Conver.ts new file mode 100644 index 00000000..3a487c3b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/base64Conver.ts @@ -0,0 +1,46 @@ +/** + * @description: base64 to blob + */ +export function dataURLtoBlob(base64Buf: string): Blob { + const arr = base64Buf.split(','); + const typeItem = arr[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const mime = typeItem!.match(/:(.*?);/)![1]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const bstr = window.atob(arr[1]!); + let n = bstr.length; + const u8arr = new Uint8Array(n); + while (n--) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + u8arr[n] = bstr.codePointAt(n)!; + } + return new Blob([u8arr], { type: mime }); +} + +/** + * img url to base64 + * @param url + */ +export function urlToBase64(url: string, mineType?: string): Promise { + return new Promise((resolve, reject) => { + let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const ctx = canvas!.getContext('2d'); + + const img = new Image(); + img.crossOrigin = ''; + img.addEventListener('load', () => { + if (!canvas || !ctx) { + // eslint-disable-next-line prefer-promise-reject-errors + return reject(); + } + canvas.height = img.height; + canvas.width = img.width; + ctx.drawImage(img, 0, 0); + const dataURL = canvas.toDataURL(mineType || 'image/png'); + canvas = null; + resolve(dataURL); + }); + img.src = url; + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/download.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/download.ts new file mode 100644 index 00000000..56db953f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/download.ts @@ -0,0 +1,268 @@ +import type { VbenFormProps } from '#/adapter/form'; + +import { $t } from '@vben/locales'; +import { cloneDeep, formatDate } from '@vben/utils'; + +import { message } from 'ant-design-vue'; +import { isFunction } from 'lodash-es'; + +import { dataURLtoBlob, urlToBase64 } from './base64Conver'; + +/** + * + * @deprecated 无法处理区间选择器数据 请使用commonDownloadExcel + * + * 下载excel文件 + * @param [func] axios函数 + * @param [fileName] 文件名称 不需要带xlsx后缀 + * @param [requestData] 请求参数 + * @param [withRandomName] 是否带随机文件名 + * + * @return void + */ +export async function downloadExcel( + func: (data?: any) => Promise, + fileName: string, + requestData: any = {}, + withRandomName = true, +) { + const hideLoading = message.loading($t('pages.common.downloadLoading'), 0); + try { + const data = await func(requestData); + downloadExcelFile(data, fileName, withRandomName); + } catch (error) { + console.error(error); + } finally { + hideLoading(); + } +} + +/** + * 源码同packages\@core\ui-kit\form-ui\src\components\form-actions.vue + * @param values 表单值 + * @param fieldMappingTime 区间选择器 字段映射 + * @returns 格式化后的值 + */ +function handleRangeTimeValue( + values: Record, + fieldMappingTime: VbenFormProps['fieldMappingTime'], +) { + // 需要深拷贝 可能是readonly的 + values = cloneDeep(values); + if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { + return values; + } + + fieldMappingTime.forEach( + ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => { + if (startTimeKey && endTimeKey && values[field] === null) { + Reflect.deleteProperty(values, startTimeKey); + Reflect.deleteProperty(values, endTimeKey); + // delete values[startTimeKey]; + // delete values[endTimeKey]; + } + + if (!values[field]) { + Reflect.deleteProperty(values, field); + // delete values[field]; + return; + } + + const [startTime, endTime] = values[field]; + if (format === null) { + values[startTimeKey] = startTime; + values[endTimeKey] = endTime; + } else if (isFunction(format)) { + values[startTimeKey] = format(startTime, startTimeKey); + values[endTimeKey] = format(endTime, endTimeKey); + } else { + const [startTimeFormat, endTimeFormat] = Array.isArray(format) + ? format + : [format, format]; + + values[startTimeKey] = startTime + ? formatDate(startTime, startTimeFormat) + : undefined; + values[endTimeKey] = endTime + ? formatDate(endTime, endTimeFormat) + : undefined; + } + // delete values[field]; + Reflect.deleteProperty(values, field); + }, + ); + return values; +} + +export interface DownloadExcelOptions { + // 是否随机文件名(带时间戳) + withRandomName?: boolean; + // 区间选择器 字段映射 + fieldMappingTime?: VbenFormProps['fieldMappingTime']; +} + +/** + * 通用下载excel方法 + * @param api 后端下载接口 + * @param fileName 文件名 不带拓展名 + * @param requestData 请求参数 + * @param options 下载选项 + */ +export async function commonDownloadExcel( + api: (data?: any) => Promise, + fileName: string, + requestData: any = {}, + options: DownloadExcelOptions = {}, +) { + const hideLoading = message.loading($t('pages.common.downloadLoading'), 0); + try { + const { withRandomName = true, fieldMappingTime } = options; + // 需要处理时间字段映射 + const data = await api(handleRangeTimeValue(requestData, fieldMappingTime)); + downloadExcelFile(data, fileName, withRandomName); + } catch (error) { + console.error(error); + } finally { + hideLoading(); + } +} + +export function downloadExcelFile( + data: BlobPart, + filename: string, + withRandomName = true, +) { + let realFileName = filename; + if (withRandomName) { + realFileName = `${filename}-${Date.now()}.xlsx`; + } + downloadByData(data, realFileName); +} + +/** + * Download online pictures + * @param url + * @param filename + * @param mime + * @param bom + */ +export function downloadByOnlineUrl( + url: string, + filename: string, + mime?: string, + bom?: BlobPart, +) { + urlToBase64(url).then((base64) => { + downloadByBase64(base64, filename, mime, bom); + }); +} + +/** + * Download pictures based on base64 + * @param buf + * @param filename + * @param mime + * @param bom + */ +export function downloadByBase64( + buf: string, + filename: string, + mime?: string, + bom?: BlobPart, +) { + const base64Buf = dataURLtoBlob(buf); + downloadByData(base64Buf, filename, mime, bom); +} + +/** + * Download according to the background interface file stream + * @param {*} data + * @param {*} filename + * @param {*} mime + * @param {*} bom + */ +export function downloadByData( + data: BlobPart, + filename: string, + mime?: string, + bom?: BlobPart, +) { + const blobData = bom === undefined ? [data] : [bom, data]; + const blob = new Blob(blobData, { type: mime || 'application/octet-stream' }); + + const blobURL = window.URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.style.display = 'none'; + tempLink.href = blobURL; + tempLink.setAttribute('download', filename); + if (tempLink.download === undefined) { + tempLink.setAttribute('target', '_blank'); + } + document.body.append(tempLink); + tempLink.click(); + tempLink.remove(); + window.URL.revokeObjectURL(blobURL); +} + +export function openWindow( + url: string, + opt?: { + noopener?: boolean; + noreferrer?: boolean; + target?: '_blank' | '_self' | string; + }, +) { + const { noopener = true, noreferrer = true, target = '__blank' } = opt || {}; + const feature: string[] = []; + + noopener && feature.push('noopener=yes'); + noreferrer && feature.push('noreferrer=yes'); + + window.open(url, target, feature.join(',')); +} + +/** + * Download file according to file address + * @param {*} sUrl + */ +export function downloadByUrl({ + fileName, + target = '_blank', + url, +}: { + fileName?: string; + target?: '_blank' | '_self'; + url: string; +}): boolean { + const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome'); + const isSafari = window.navigator.userAgent.toLowerCase().includes('safari'); + + if (/iP/.test(window.navigator.userAgent)) { + console.error('Your browser does not support download!'); + return false; + } + if (isChrome || isSafari) { + const link = document.createElement('a'); + link.href = url; + link.target = target; + + if (link.download !== undefined) { + link.download = + // eslint-disable-next-line unicorn/prefer-string-slice + fileName || url.substring(url.lastIndexOf('/') + 1, url.length); + } + + if (document.createEvent) { + const e = document.createEvent('MouseEvents'); + e.initEvent('click', true, true); + link.dispatchEvent(e); + return true; + } + } + if (!url.includes('?')) { + url += '?download'; + } + + openWindow(url, { target }); + return true; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/index.ts new file mode 100644 index 00000000..dab7d297 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/file/index.ts @@ -0,0 +1,31 @@ +/** + * 计算文件大小并以适当单位表示 + * + * 此函数接收一个表示文件大小的数字(以字节为单位),并返回一个格式化后的字符串, + * 该字符串表示文件的大小,以最适合的单位(B, KB, MB, GB, TB)表示 + * + * @param size 文件大小,以字节为单位 + * @param isInteger 是否返回整数大小,默认为false如果设置为true, + * 则返回的大小将不包含小数部分;如果为false,则根据单位的不同, + * 返回最多3位小数的大小 + * @returns 格式化后的文件大小字符串,如"4.5KB"或"3MB" + */ +export function calculateFileSize(size: number, isInteger = false) { + // 定义文件大小的单位数组 + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + // 定义换算基数,1KB = 1024B,1MB = 1024KB,以此类推 + const base = 1024; + + // 初始化单位索引,初始值为0,即默认单位为B + let unitIndex = 0; + // 当文件大小大于等于基数且单位索引未超出单位数组范围时,循环进行单位转换 + while (size >= base && unitIndex < units.length - 1) { + size /= base; + unitIndex++; + } + + // 根据是否需要整数大小,确定输出的精度 + const precision = isInteger ? 0 : Math.min(unitIndex, 3); + // 返回格式化后的文件大小字符串 + return `${size.toFixed(precision)}${units[unitIndex]}`; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/modal.tsx b/Yi.Vben5.Vue3/apps/web-antd/src/utils/modal.tsx new file mode 100644 index 00000000..a3c3b41e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/modal.tsx @@ -0,0 +1,64 @@ +import type { ModalFuncProps } from 'ant-design-vue'; +import type { Rule } from 'ant-design-vue/es/form'; + +import { reactive } from 'vue'; + +import { Alert, Form, Input, Modal } from 'ant-design-vue'; +import { isFunction } from 'lodash-es'; + +export interface ConfirmModalProps extends Omit { + confirmText?: string; + placeholder?: string; + onValidated?: () => Promise; +} + +export function confirmDeleteModal(props: ConfirmModalProps) { + const placeholder = props.placeholder || `输入'确认删除'`; + const confirmText = props.confirmText || '确认删除'; + + const formValue = reactive({ + content: '', + }); + const rulesRef = reactive<{ [key: string]: Rule[] }>({ + content: [ + { + message: '校验不通过', + required: true, + trigger: 'change', + validator(_, value) { + if (value !== confirmText) { + return Promise.reject(new Error('校验不通过')); + } + return Promise.resolve(); + }, + }, + ], + }); + const useForm = Form.useForm; + const { validate, validateInfos } = useForm(formValue, rulesRef); + + Modal.confirm({ + ...props, + centered: true, + content: ( +
+ +
+ + + +
+
+ ), + okButtonProps: { danger: true, type: 'primary' }, + onOk: async () => { + await validate(); + isFunction(props.onValidated) && props.onValidated(); + }, + title: '提示', + type: 'warning', + }); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/popup.ts b/Yi.Vben5.Vue3/apps/web-antd/src/utils/popup.ts new file mode 100644 index 00000000..bfe71a48 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/popup.ts @@ -0,0 +1,126 @@ +import type { ExtendedFormApi } from '@vben/common-ui'; +import type { MaybePromise } from '@vben/types'; + +import { ref } from 'vue'; + +import { $t } from '@vben/locales'; + +import { Modal } from 'ant-design-vue'; +import { isFunction } from 'lodash-es'; + +interface BeforeCloseDiffProps { + /** + * 初始化值如何获取 + * @returns Promise + */ + initializedGetter: () => MaybePromise; + /** + * 当前值如何获取 + * @returns Promise + */ + currentGetter: () => MaybePromise; + /** + * 自定义比较函数 + * @param init 初始值 + * @param current 当前值 + * @returns boolean + */ + compare?: (init: string, current: string) => boolean; +} + +/** + * 用于Drawer/Modal使用 判断表单是否有变动来决定是否弹窗提示 + * @param props props + * @returns hook + */ +export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { + const { initializedGetter, currentGetter, compare } = props; + /** + * 记录初始值 json + */ + const initialized = ref(''); + /** + * 是否已经初始化了 通过这个值判断是否需要进行对比 为false直接关闭 不弹窗 + */ + const isInitialized = ref(false); + + /** + * 标记是否已经完成初始化 后续需要进行对比 + * @param data 自定义初始化数据 可选 + */ + async function markInitialized(data?: string) { + initialized.value = data || (await initializedGetter()); + isInitialized.value = true; + } + + /** + * 重置初始化状态 需要在closed前调用 或者打开窗口时 + */ + function resetInitialized() { + initialized.value = ''; + isInitialized.value = false; + } + + /** + * 提供给useVbenForm/useVbenDrawer使用 + * @returns 是否允许关闭 + */ + async function onBeforeClose(): Promise { + // 如果还未初始化,直接允许关闭 + if (!isInitialized.value) { + return true; + } + + try { + // 获取当前表单数据 + const current = await currentGetter(); + // 自定义比较的情况 + if (isFunction(compare) && compare(initialized.value, current)) { + return true; + } else { + // 如果数据没有变化,直接允许关闭 + if (current === initialized.value) { + return true; + } + } + + // 数据有变化,显示确认对话框 + return new Promise((resolve) => { + Modal.confirm({ + title: $t('pages.common.tip'), + content: $t('pages.common.beforeCloseTip'), + centered: true, + okButtonProps: { danger: true }, + cancelText: $t('common.cancel'), + okText: $t('common.confirm'), + onOk: () => { + resolve(true); + isInitialized.value = false; + }, + onCancel: () => resolve(false), + }); + }); + } catch (error) { + console.error('Failed to compare data:', error); + return true; + } + } + + return { + onBeforeClose, + markInitialized, + resetInitialized, + }; +} + +/** + * 给useVbenForm使用的 封装函数 + * @param formApi 表单实例 + * @returns getter + */ +export function defaultFormValueGetter(formApi: ExtendedFormApi) { + return async () => { + const v = await formApi.getValues(); + return JSON.stringify(v); + }; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/utils/render.tsx b/Yi.Vben5.Vue3/apps/web-antd/src/utils/render.tsx new file mode 100644 index 00000000..419daf31 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/utils/render.tsx @@ -0,0 +1,230 @@ +import type { Component as ComponentType } from 'vue'; + +import type { DictData } from '#/api/system/dict/dict-data-model'; + +import { h } from 'vue'; + +import { JsonPreview } from '@vben/common-ui'; +import { + AndroidIcon, + BaiduIcon, + ChromeIcon, + DefaultBrowserIcon, + DefaultOsIcon, + DingtalkIcon, + EdgeIcon, + FirefoxIcon, + IconifyIcon, + IPhoneIcon, + LinuxIcon, + MicromessengerIcon, + OperaIcon, + OSXIcon, + QuarkIcon, + SafariIcon, + SvgQQIcon, + UcIcon, + WindowsIcon, +} from '@vben/icons'; + +import { Tag } from 'ant-design-vue'; + +import { DictTag } from '#/components/dict'; + +import { getDictOptions } from './dict'; + +/** + * 渲染标签 + * @param text 文字 + * @param color 颜色 + * @returns render + */ +function renderTag(text: string, color?: string) { + return {text}; +} + +/** + * + * @param tags 标签list + * @param wrap 是否换行显示 + * @param [gap] 间隔 + * @returns render + */ +export function renderTags(tags: string[], wrap = false, gap = 1) { + return ( +
+ {tags.map((tag, index) => { + return
{renderTag(tag)}
; + })} +
+ ); +} + +/** + * + * @param json json对象 接受object/string类型 + * @returns json预览 + */ +export function renderJsonPreview(json: any) { + if (typeof json !== 'object' && typeof json !== 'string') { + return {json}; + } + if (typeof json === 'object') { + return ; + } + try { + const obj = JSON.parse(json); + // 基本数据类型可以被转为json + if (typeof obj !== 'object') { + return {obj}; + } + return ; + } catch { + return {json}; + } +} + +/** + * iconify图标 + * @param icon icon名称 + * @returns render + */ +export function renderIcon(icon: string) { + return ; +} + +/** + * httpMethod标签 + * @param type method类型 + * @returns render + */ +export function renderHttpMethodTag(type: string) { + const method = type.toUpperCase(); + const colors: { [key: string]: string } = { + DELETE: 'red', + GET: 'green', + POST: 'blue', + PUT: 'orange', + }; + + const color = colors[method] ?? 'default'; + const title = `${method}请求`; + + return {title}; +} + +export function renderDictTag(value: number | string, dicts: DictData[]) { + return ; +} + +/** + * render多个dictTag + * @param value key数组 string[]类型 + * @param dicts 字典数组 + * @param wrap 是否需要换行显示 + * @param [gap] 间隔 + * @returns render + */ +export function renderDictTags( + value: string[], + dicts: DictData[], + wrap = true, + gap = 1, +) { + if (!Array.isArray(value)) { + return
{value}
; + } + return ( +
+ {value.map((item, index) => { + return
{renderDictTag(item, dicts)}
; + })} +
+ ); +} + +/** + * 显示字典标签 一般是table使用 + * @param value 值 + * @param dictName dictName + * @returns tag + */ +export function renderDict(value: number | string, dictName: string) { + const dictInfo = getDictOptions(dictName); + return renderDictTag(value, dictInfo); +} +export function renderIconSpan( + icon: ComponentType, + value: string, + center = false, + marginLeft = '2px', +) { + const justifyCenter = center ? 'justify-center' : ''; + + return ( + + {h(icon)} + {value} + + ); +} + +const osOptions = [ + { icon: WindowsIcon, value: 'windows' }, + { icon: LinuxIcon, value: 'linux' }, + { icon: OSXIcon, value: 'osx' }, + { icon: AndroidIcon, value: 'android' }, + { icon: IPhoneIcon, value: 'iphone' }, +]; + +/** + * 浏览器图标 + * cn.hutool.http.useragent -> browers + */ +const browserOptions = [ + { icon: ChromeIcon, value: 'chrome' }, + { icon: EdgeIcon, value: 'edge' }, + { icon: FirefoxIcon, value: 'firefox' }, + { icon: OperaIcon, value: 'opera' }, + { icon: SafariIcon, value: 'safari' }, + { icon: MicromessengerIcon, value: 'micromessenger' }, + { icon: MicromessengerIcon, value: 'windowswechat' }, + { icon: QuarkIcon, value: 'quark' }, + { icon: MicromessengerIcon, value: 'wxwork' }, + { icon: SvgQQIcon, value: 'qq' }, + { icon: DingtalkIcon, value: 'dingtalk' }, + { icon: UcIcon, value: 'uc' }, + { icon: BaiduIcon, value: 'baidu' }, +]; + +export function renderOsIcon(os: string, center = false) { + if (!os) { + return; + } + let current = osOptions.find((item) => + os.toLocaleLowerCase().includes(item.value), + ); + // windows要特殊处理 + if (os.toLocaleLowerCase().includes('windows')) { + current = osOptions[0]; + } + const icon = current ? current.icon : DefaultOsIcon; + return renderIconSpan(icon, os, center, '5px'); +} + +export function renderBrowserIcon(browser: string, center = false) { + if (!browser) { + return; + } + const current = browserOptions.find((item) => + browser.toLocaleLowerCase().includes(item.value), + ); + const icon = current ? current.icon : DefaultBrowserIcon; + return renderIconSpan(icon, browser, center, '5px'); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/README.md b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/README.md new file mode 100644 index 00000000..8248afe6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/README.md @@ -0,0 +1,3 @@ +# \_core + +此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/about/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/about/index.vue new file mode 100644 index 00000000..0ee52433 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/about/index.vue @@ -0,0 +1,9 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/code-login.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/code-login.vue new file mode 100644 index 00000000..657a902d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/code-login.vue @@ -0,0 +1,144 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/forget-password.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/forget-password.vue new file mode 100644 index 00000000..0958e89b --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/forget-password.vue @@ -0,0 +1,42 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/login.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/login.vue new file mode 100644 index 00000000..6aedf91d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/login.vue @@ -0,0 +1,180 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/oauth-login.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/oauth-login.vue new file mode 100644 index 00000000..6c4d553d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/oauth-login.vue @@ -0,0 +1,44 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/qrcode-login.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/qrcode-login.vue new file mode 100644 index 00000000..23f5f2da --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/qrcode-login.vue @@ -0,0 +1,10 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/register.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/register.vue new file mode 100644 index 00000000..f264c467 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/authentication/register.vue @@ -0,0 +1,95 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/coming-soon.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/coming-soon.vue new file mode 100644 index 00000000..f394930f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/coming-soon.vue @@ -0,0 +1,7 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/forbidden.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/forbidden.vue new file mode 100644 index 00000000..8ea65fed --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/forbidden.vue @@ -0,0 +1,9 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/internal-error.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/internal-error.vue new file mode 100644 index 00000000..819a47d5 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/internal-error.vue @@ -0,0 +1,9 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/not-found.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/not-found.vue new file mode 100644 index 00000000..4d178e9c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/not-found.vue @@ -0,0 +1,9 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/offline.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/offline.vue new file mode 100644 index 00000000..5de4a88d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/fallback/offline.vue @@ -0,0 +1,9 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/oauth-common.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/oauth-common.ts new file mode 100644 index 00000000..64c3ef30 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/oauth-common.ts @@ -0,0 +1,102 @@ +import type { Component, CSSProperties } from 'vue'; + +import { markRaw, ref } from 'vue'; + +import { DEFAULT_TENANT_ID } from '@vben/constants'; +import { + GiteeIcon, + GithubOAuthIcon, + SvgMaxKeyIcon, + SvgTopiamIcon, + SvgWechatIcon, +} from '@vben/icons'; + +import { createGlobalState } from '@vueuse/core'; + +import { authBinding } from '#/api/core/auth'; + +/** + * @description: oauth登录 + * @param title 标题 + * @param description 描述 + * @param avatar 图标 + * @param color 图标颜色可直接写英文颜色/hex + */ +export interface ListItem { + title: string; + description: string; + avatar?: Component; + style?: CSSProperties; +} + +/** + * @description: 绑定账号 + * @param source 来源 如gitee github 与后端的social-callback?source=xxx对应 + * @param bound 是否已经绑定 + */ +export interface BindItem extends ListItem { + source: string; + bound?: boolean; +} + +/** + * 这里存储登录页的tenantId 由于个人中心也会用到 需要共享 + * 所以使用`createGlobalState` + * @see https://vueuse.org/shared/createGlobalState/ + */ +export const useLoginTenantId = createGlobalState(() => { + const loginTenantId = ref(DEFAULT_TENANT_ID); + + return { + loginTenantId, + }; +}); + +/** + * 绑定授权 + * @param source + */ +export async function handleAuthBinding(source: string) { + const { loginTenantId } = useLoginTenantId(); + // 这里返回打开授权页面的链接 + const href = await authBinding(source, loginTenantId.value); + window.location.href = href; +} + +/** + * 账号绑定 list + * 添加账号绑定只需要在这里增加即可 + */ +export const accountBindList: BindItem[] = [ + { + avatar: markRaw(GiteeIcon), + description: '绑定Gitee账号', + source: 'gitee', + title: 'Gitee', + style: { color: '#c71d23' }, + }, + { + avatar: markRaw(GithubOAuthIcon), + description: '绑定Github账号', + source: 'github', + title: 'Github', + }, + { + avatar: markRaw(SvgMaxKeyIcon), + description: '绑定MaxKey账号', + source: 'maxkey', + title: 'MaxKey', + }, + { + avatar: markRaw(SvgTopiamIcon), + description: '绑定topiam账号', + source: 'topiam', + title: 'Topiam', + }, + { + avatar: markRaw(SvgWechatIcon), + description: '绑定wechat账号', + source: 'wechat', + title: 'Wechat', + }, +]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/account-bind.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/account-bind.vue new file mode 100644 index 00000000..0da901c6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/account-bind.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/base-setting.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/base-setting.vue new file mode 100644 index 00000000..1801b232 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/base-setting.vue @@ -0,0 +1,120 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/online-device.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/online-device.vue new file mode 100644 index 00000000..926f2bab --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/online-device.vue @@ -0,0 +1,52 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/secure-setting.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/secure-setting.vue new file mode 100644 index 00000000..58583072 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/components/secure-setting.vue @@ -0,0 +1,106 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/index.vue new file mode 100644 index 00000000..a3af56cb --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/mitt.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/mitt.ts new file mode 100644 index 00000000..9499b91d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/mitt.ts @@ -0,0 +1,7 @@ +import { mitt } from '@vben/utils'; + +type Events = { + updateProfile: void; +}; + +export const emitter = mitt(); diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/profile-panel.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/profile-panel.vue new file mode 100644 index 00000000..c1e9b2f7 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/profile-panel.vue @@ -0,0 +1,84 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/setting-panel.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/setting-panel.vue new file mode 100644 index 00000000..10408ae4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/profile/setting-panel.vue @@ -0,0 +1,39 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/social-callback/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/social-callback/index.vue new file mode 100644 index 00000000..cb9fafd4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/_core/social-callback/index.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue new file mode 100644 index 00000000..9bd90fc8 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-trends.vue @@ -0,0 +1,97 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue new file mode 100644 index 00000000..651abbc1 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-data.vue @@ -0,0 +1,81 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue new file mode 100644 index 00000000..f38b5c03 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -0,0 +1,45 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue new file mode 100644 index 00000000..d97c3c0f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits-source.vue @@ -0,0 +1,64 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue new file mode 100644 index 00000000..fc961c83 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/analytics-visits.vue @@ -0,0 +1,54 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/index.vue new file mode 100644 index 00000000..5e3d6d28 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/analytics/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/workspace/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/workspace/index.vue new file mode 100644 index 00000000..b95d6138 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/dashboard/workspace/index.vue @@ -0,0 +1,266 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/index.ts new file mode 100644 index 00000000..4590457c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/index.ts @@ -0,0 +1,60 @@ +import type { DemoForm, DemoQuery, DemoVO } from './model'; + +import type { ID, IDS, PageResult } from '#/api/common'; + +import { commonExport } from '#/api/helper'; +import { requestClient } from '#/api/request'; + +/** + * 查询测试单表列表 + * @param params + * @returns 测试单表列表 + */ +export function demoList(params?: DemoQuery) { + return requestClient.get>('/demo/demo/list', { params }); +} + +/** + * 导出测试单表列表 + * @param params + * @returns 测试单表列表 + */ +export function demoExport(params?: DemoQuery) { + return commonExport('/demo/demo/export', params ?? {}); +} + +/** + * 查询测试单表详情 + * @param id id + * @returns 测试单表详情 + */ +export function demoInfo(id: ID) { + return requestClient.get(`/demo/demo/${id}`); +} + +/** + * 新增测试单表 + * @param data + * @returns void + */ +export function demoAdd(data: DemoForm) { + return requestClient.postWithMsg('/demo/demo', data); +} + +/** + * 更新测试单表 + * @param data + * @returns void + */ +export function demoUpdate(data: DemoForm) { + return requestClient.putWithMsg('/demo/demo', data); +} + +/** + * 删除测试单表 + * @param id id + * @returns void + */ +export function demoRemove(id: ID | IDS) { + return requestClient.deleteWithMsg(`/demo/demo/${id}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/model.d.ts new file mode 100644 index 00000000..e7f69c48 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/api/model.d.ts @@ -0,0 +1,82 @@ +import type { BaseEntity, PageQuery } from '#/api/common'; + +export interface DemoVO { + /** + * 主键 + */ + id: number | string; + + /** + * 排序号 + */ + orderNum: number; + + /** + * key键 + */ + testKey: string; + + /** + * 值 + */ + value: string; + + /** + * 版本 + */ + version: number; +} + +export interface DemoForm extends BaseEntity { + /** + * 主键 + */ + id?: number | string; + + /** + * 排序号 + */ + orderNum?: number; + + /** + * key键 + */ + testKey?: string; + + /** + * 值 + */ + value?: string; + + /** + * 版本 + */ + version?: number; +} + +export interface DemoQuery extends PageQuery { + /** + * 排序号 + */ + orderNum?: number; + + /** + * key键 + */ + testKey?: string; + + /** + * 值 + */ + value?: string; + + /** + * 版本 + */ + version?: number; + + /** + * 日期范围参数 + */ + params?: any; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/data.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/data.ts new file mode 100644 index 00000000..241d855f --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/data.ts @@ -0,0 +1,93 @@ +import type { FormSchemaGetter } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +export const querySchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'orderNum', + label: '排序号', + }, + { + component: 'Input', + fieldName: 'testKey', + label: 'key键', + }, + { + component: 'Input', + fieldName: 'value', + label: '值', + }, + { + component: 'Input', + fieldName: 'version', + label: '版本', + }, +]; + +export const columns: VxeGridProps['columns'] = [ + { type: 'checkbox', width: 60 }, + { + title: '主键', + field: 'id', + }, + { + title: '排序号', + field: 'orderNum', + }, + { + title: 'key键', + field: 'testKey', + }, + { + title: '值', + field: 'value', + }, + { + title: '版本', + field: 'version', + }, + { + field: 'action', + fixed: 'right', + slots: { default: 'action' }, + title: '操作', + resizable: false, + width: 'auto', + }, +]; + +export const modalSchema: FormSchemaGetter = () => [ + { + label: '主键', + fieldName: 'id', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '排序号', + fieldName: 'orderNum', + component: 'Input', + rules: 'required', + }, + { + label: 'key键', + fieldName: 'testKey', + component: 'Input', + rules: 'required', + }, + { + label: '值', + fieldName: 'value', + component: 'Input', + rules: 'required', + }, + { + label: '版本', + fieldName: 'version', + component: 'Input', + rules: 'required', + }, +]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/demo-modal.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/demo-modal.vue new file mode 100644 index 00000000..8126362d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/demo-modal.vue @@ -0,0 +1,87 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/index.vue new file mode 100644 index 00000000..115819f6 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/demo/index.vue @@ -0,0 +1,165 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/index.ts new file mode 100644 index 00000000..3df12a26 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/index.ts @@ -0,0 +1,50 @@ +import type { TreeForm, TreeQuery, TreeVO } from './model'; + +import type { ID, IDS } from '#/api/common'; + +import { requestClient } from '#/api/request'; + +/** + * 查询测试树列表 + * @param params + * @returns 测试树列表 + */ +export function treeList(params?: TreeQuery) { + return requestClient.get('/demo/tree/list', { params }); +} + +/** + * 查询测试树详情 + * @param id id + * @returns 测试树详情 + */ +export function treeInfo(id: ID) { + return requestClient.get(`/demo/tree/${id}`); +} + +/** + * 新增测试树 + * @param data + * @returns void + */ +export function treeAdd(data: TreeForm) { + return requestClient.postWithMsg('/demo/tree', data); +} + +/** + * 更新测试树 + * @param data + * @returns void + */ +export function treeUpdate(data: TreeForm) { + return requestClient.putWithMsg('/demo/tree', data); +} + +/** + * 删除测试树 + * @param id id + * @returns void + */ +export function treeRemove(id: ID | IDS) { + return requestClient.deleteWithMsg(`/demo/tree/${id}`); +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/model.d.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/model.d.ts new file mode 100644 index 00000000..87ba83db --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/api/model.d.ts @@ -0,0 +1,102 @@ +import type { BaseEntity } from '#/api/common'; + +export interface TreeVO { + /** + * 主键 + */ + id: number | string; + + /** + * 父id + */ + parentId: number | string; + + /** + * 部门id + */ + deptId: number | string; + + /** + * 用户id + */ + userId: number | string; + + /** + * 值 + */ + treeName: string; + + /** + * 版本 + */ + version: number; + + /** + * 子对象 + */ + children: TreeVO[]; +} + +export interface TreeForm extends BaseEntity { + /** + * 主键 + */ + id?: number | string; + + /** + * 父id + */ + parentId?: number | string; + + /** + * 部门id + */ + deptId?: number | string; + + /** + * 用户id + */ + userId?: number | string; + + /** + * 值 + */ + treeName?: string; + + /** + * 版本 + */ + version?: number; +} + +export interface TreeQuery { + /** + * 父id + */ + parentId?: number | string; + + /** + * 部门id + */ + deptId?: number | string; + + /** + * 用户id + */ + userId?: number | string; + + /** + * 值 + */ + treeName?: string; + + /** + * 版本 + */ + version?: number; + + /** + * 日期范围参数 + */ + params?: any; +} diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/data.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/data.ts new file mode 100644 index 00000000..ae15288e --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/data.ts @@ -0,0 +1,108 @@ +import type { FormSchemaGetter } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +export const querySchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'parentId', + label: '父id', + }, + { + component: 'Input', + fieldName: 'deptId', + label: '部门id', + }, + { + component: 'Input', + fieldName: 'userId', + label: '用户id', + }, + { + component: 'Input', + fieldName: 'treeName', + label: '值', + }, + { + component: 'Input', + fieldName: 'version', + label: '版本', + }, +]; + +export const columns: VxeGridProps['columns'] = [ + { + title: '主键', + field: 'id', + treeNode: true, + }, + { + title: '父id', + field: 'parentId', + }, + { + title: '部门id', + field: 'deptId', + }, + { + title: '用户id', + field: 'userId', + }, + { + title: '值', + field: 'treeName', + }, + { + title: '版本', + field: 'version', + }, + { + field: 'action', + fixed: 'right', + slots: { default: 'action' }, + title: '操作', + resizable: false, + width: 'auto', + }, +]; + +export const modalSchema: FormSchemaGetter = () => [ + { + label: '主键', + fieldName: 'id', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '父id', + fieldName: 'parentId', + component: 'TreeSelect', + rules: 'required', + }, + { + label: '部门id', + fieldName: 'deptId', + component: 'Input', + rules: 'required', + }, + { + label: '用户id', + fieldName: 'userId', + component: 'Input', + rules: 'required', + }, + { + label: '值', + fieldName: 'treeName', + component: 'Input', + rules: 'required', + }, + { + label: '版本', + fieldName: 'version', + component: 'Input', + rules: 'required', + }, +]; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/index.vue new file mode 100644 index 00000000..c04ce0cd --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/index.vue @@ -0,0 +1,146 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/tree-modal.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/tree-modal.vue new file mode 100644 index 00000000..bfe628c0 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/demo/tree/tree-modal.vue @@ -0,0 +1,104 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/admin/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/admin/index.vue new file mode 100644 index 00000000..058f6d4a --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/admin/index.vue @@ -0,0 +1,7 @@ + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/command-chart.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/command-chart.vue new file mode 100644 index 00000000..a2ae46b4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/command-chart.vue @@ -0,0 +1,63 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/index.ts b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/index.ts new file mode 100644 index 00000000..1978212c --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/index.ts @@ -0,0 +1,3 @@ +export { default as CommandChart } from './command-chart.vue'; +export { default as MemoryChart } from './memory-chart.vue'; +export { default as RedisDescription } from './redis-description.vue'; diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/memory-chart.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/memory-chart.vue new file mode 100644 index 00000000..b9582be4 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/memory-chart.vue @@ -0,0 +1,89 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/redis-description.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/redis-description.vue new file mode 100644 index 00000000..784cae05 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/components/redis-description.vue @@ -0,0 +1,58 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/index.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/index.vue new file mode 100644 index 00000000..9ac45865 --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/list.vue b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/list.vue new file mode 100644 index 00000000..ba59ba6d --- /dev/null +++ b/Yi.Vben5.Vue3/apps/web-antd/src/views/monitor/cache/list.vue @@ -0,0 +1,318 @@ + + +