@oj-module/cli 0.2.64 → 0.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/.oj-version +1 -0
- package/bin/oj +4 -0
- package/devkit/README.md +18 -4
- package/devkit/SKILL.md +35 -3
- package/devkit/framework-dev.md +22 -11
- package/devkit/manual.md +161 -68
- package/devkit/menu-rules.md +4 -1
- package/devkit/publish-walkthrough.md +110 -37
- package/devkit/wecom-internal-app.md +7 -2
- package/devkit/wecom-multi-corp-links.md +163 -0
- package/package.json +3 -2
- package/shell-dist/assets/ahooks.2l850ymmh0.js +18 -0
- package/shell-dist/assets/antd-es-locale-zh_CN.1gjjsvxbf0.js +1 -0
- package/shell-dist/assets/antd-img-crop.196rkty480.js +99 -0
- package/shell-dist/assets/antd-locale-en_US.561tozx860.js +1 -0
- package/shell-dist/assets/antd-locale-zh_CN.1cmpmgzh70.js +1 -0
- package/shell-dist/assets/antd.uw1coq15l0.js +348 -0
- package/shell-dist/assets/clsx.jotilrt2e0.js +1 -0
- package/shell-dist/assets/contract-errors.2o17vz9u50.js +1 -0
- package/shell-dist/assets/contract.3h327o3mj0.js +57 -0
- package/shell-dist/assets/cssinjs.atgmhan7m0.js +3 -0
- package/shell-dist/assets/dayjs-plugin-advancedFormat.3vsyd2eis0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-customParseFormat.13xhghv1d0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-isoWeek.1fn1tw82p0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-localeData.2rwbjp9nk0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-quarterOfYear.2oqo1lcx80.js +1 -0
- package/shell-dist/assets/dayjs-plugin-relativeTime.2pqs3et4y0.js +1 -0
- package/shell-dist/assets/dayjs-plugin-weekOfYear.gvn9iph080.js +1 -0
- package/shell-dist/assets/dayjs-plugin-weekYear.2u78l3ex10.js +1 -0
- package/shell-dist/assets/dayjs-plugin-weekday.2d56a4tml0.js +1 -0
- package/shell-dist/assets/dayjs.pa60l6wjo0.js +1 -0
- package/shell-dist/assets/dnd-kit-core.1xyreig6r0.js +5 -0
- package/shell-dist/assets/dnd-kit-sortable.s94qciuft0.js +1 -0
- package/shell-dist/assets/dnd-kit-utilities.1umw4xphb0.js +1 -0
- package/shell-dist/assets/i18next.33zqofapj0.js +1 -0
- package/shell-dist/assets/icons-es-components-Context.365qmflwg0.js +1 -0
- package/shell-dist/assets/icons.159wjwals0.js +57 -0
- package/shell-dist/assets/{index-xZhmujjM.js → index.6alqyx6ae0.js} +21 -1
- package/shell-dist/assets/jsx-dev-runtime.3uibuc82f0.js +14 -0
- package/shell-dist/assets/jsx-runtime.h40xu4o5b0.js +14 -0
- package/shell-dist/assets/keepalive-for-react.xenu5vam70.js +1 -0
- package/shell-dist/assets/kit.37kr8ui390.js +1 -0
- package/shell-dist/assets/ky.1cgch7gu60.js +6 -0
- package/shell-dist/assets/motion-react.pjsxytvjs0.js +12 -0
- package/shell-dist/assets/motion.21tciqutg0.js +4 -0
- package/shell-dist/assets/nprogress.2nz76n9ny0.js +7 -0
- package/shell-dist/assets/ojm-dayjs-locale-zh-cn.3vr3beiyp0.js +9 -0
- package/shell-dist/assets/pinyin-pro.3v0mnxczn0.js +1 -0
- package/shell-dist/assets/pro-components.2kyot1vcl0.js +83 -0
- package/shell-dist/assets/react-countup.2x8idnr8y0.js +10 -0
- package/shell-dist/assets/react-dom-client.ick0v05ob0.js +43 -0
- package/shell-dist/assets/react-dom.2so9uxg3f0.js +22 -0
- package/shell-dist/assets/react-error-boundary.2dncdfj3b0.js +1 -0
- package/shell-dist/assets/react-i18next.auhvjhqv00.js +1 -0
- package/shell-dist/assets/react-jss.1ei6dr9e00.js +29 -0
- package/shell-dist/assets/react-query.3rln45ow00.js +1 -0
- package/shell-dist/assets/react-router-dom.3c6zld8h40.js +44 -0
- package/shell-dist/assets/react-router.2h9rebcv50.js +56 -0
- package/shell-dist/assets/react.1ew63mwts0.js +14 -0
- package/shell-dist/assets/runtime.2dgstpjsu0.js +21702 -0
- package/shell-dist/assets/simplebar-react.2g739uvai0.js +1 -0
- package/shell-dist/assets/spin-delay.yjeyj6iwp0.js +9 -0
- package/shell-dist/assets/tailwind-merge.125ehiqjw0.js +1 -0
- package/shell-dist/assets/use-sync-external-store-shim.1nz1pi9bb0.js +22 -0
- package/shell-dist/assets/zustand-middleware.1xaxjueme0.js +6 -0
- package/shell-dist/assets/zustand-react-shallow.pybayv1hx0.js +1 -0
- package/shell-dist/assets/zustand-react.9fsl9brxy0.js +1 -0
- package/shell-dist/assets/zustand-shallow.6cz0yyv9x0.js +1 -0
- package/shell-dist/assets/zustand-vanilla-shallow.3424monv50.js +1 -0
- package/shell-dist/assets/zustand-vanilla.1mo3ka3g30.js +1 -0
- package/shell-dist/assets/zustand.3gjgzlnqo0.js +1 -0
- package/shell-dist/index.html +67 -118
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/publish-keygen.test.ts +157 -0
- package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
- package/src/__tests__/templates-schema-columns.test.ts +15 -2
- package/src/__tests__/templates-tenant-scope.test.ts +6 -2
- package/src/bundle.ts +8 -3
- package/src/cli.ts +17 -5
- package/src/dev.ts +15 -10
- package/src/esm-exports.ts +152 -70
- package/src/install.ts +11 -4
- package/src/keygen.ts +54 -0
- package/src/lifecycle/__tests__/bundle.test.ts +21 -3
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
- package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
- package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
- package/src/lifecycle/__tests__/smoke.test.ts +2 -2
- package/src/manifest-contract.ts +91 -8
- package/src/market/bootstrap.ts +26 -2
- package/src/market/command.ts +1 -0
- package/src/market/publish.ts +5 -1
- package/src/market/walkthrough.ts +24 -8
- package/src/shared-deps.ts +69 -19
- package/src/site-base.ts +4 -0
- package/src/site.ts +2 -0
- package/src/usage.ts +8 -4
- package/src/vendor.ts +5 -1
- package/templates/config.yaml +4 -2
- package/templates/init.include.yaml +8 -3
- package/templates/openapi.json +460 -1
- package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
- package/templates/src/app/api/_platform/_lifecycle/installer.ts +71 -22
- package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
- package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
- package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
- package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
- package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
- package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
- package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
- package/templates/src/app/api/_platform/manifest.yaml +1 -0
- package/templates/src/app/api/_platform/market/api.ts +235 -10
- package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
- package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
- package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
- package/templates/src/app/api/_platform/schema.yaml +18 -0
- package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
- package/templates/src/app/api/_platform/seed.pg.sql +10 -0
- package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
- package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
- package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
- package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
- package/templates/src/app/api/home/seed.mysql.sql +71 -0
- package/templates/src/app/api/home/seed.pg.sql +71 -0
- package/templates/src/app/api/notification/_shared/notify.ts +6 -6
- package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
- package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
- package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
- package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
- package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
- package/templates/src/app/api/notification/seed.mysql.sql +8 -0
- package/templates/src/app/api/notification/seed.pg.sql +11 -0
- package/templates/src/app/api/publish/_shared/identity.ts +69 -0
- package/templates/src/app/api/publish/api.ts +349 -0
- package/templates/src/app/api/publish/manifest.yaml +8 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
- package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
- package/templates/src/app/api/publish/schema.yaml +33 -0
- package/templates/src/app/api/system/login-methods/api.ts +2 -1
- package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
- package/templates/src/app/web/_platform/__client/index.ts +25 -1
- package/templates/src/app/web/entry.ts +9 -5
- package/templates/src/app/web/home/pages/components/bar-chart.tsx +5 -3
- package/templates/src/app/web/home/pages/components/echarts-setup.ts +30 -0
- package/templates/src/app/web/home/pages/components/line-chart.tsx +5 -3
- package/templates/src/app/web/home/pages/components/pie-chart.tsx +6 -3
- package/templates/src/app/web/home/pages/index.tsx +3 -2
- package/templates/src/app/web/home/routes.ts +8 -2
- package/templates/src/app/web/login/locales/en-US.json +7 -1
- package/templates/src/app/web/login/locales/zh-CN.json +7 -1
- package/templates/src/app/web/login/pages/login.tsx +79 -86
- package/templates/src/app/web/login/routes.ts +54 -1
- package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
- package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
- package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
- package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
- package/templates/src/app/web/system/locales/en-US.json +1 -8
- package/templates/src/app/web/system/locales/zh-CN.json +1 -8
- package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
- package/templates/src/app/web/system/routes.ts +0 -6
- package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
- package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
- package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
- package/templates/src/oj-market/web/api/client.ts +3 -0
- package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
- package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
- package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
- package/templates/src/oj-market/web/entry.ts +31 -2
- package/templates/src/oj-market/web/locales/en-US.json +5 -1
- package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
- package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
- package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
- package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
- package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
- package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
- package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
- package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
- package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
- package/templates/src/oj-market/web/publish/routes.ts +41 -0
- package/templates/src/oj-market/web/types.ts +30 -0
- package/templates/tests/lifecycle/compensate.spec.ts +207 -0
- package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
- package/templates/tests/lifecycle/installer.spec.ts +28 -9
- package/templates/tests/lifecycle/market-api.spec.ts +164 -12
- package/templates/tests/lifecycle/preflight.spec.ts +68 -0
- package/templates/tests/lifecycle/role-title-translate.spec.ts +2 -32
- package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
- package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
- package/templates/tests/publish-credential.spec.ts +354 -0
- package/templates/tests/publish-identity.spec.ts +44 -0
- package/templates/tests/wecom-client-timeout.spec.ts +33 -3
- package/templates/tests/wecom-landing.spec.ts +63 -1
- package/templates/tsconfig.paths.json +1 -0
- package/templates/web.config.ts +6 -4
- package/vendor/host-versions.json +3 -3
- package/shell-dist/assets/ahooks.js +0 -6909
- package/shell-dist/assets/antd-es-locale-zh_CN.js +0 -257
- package/shell-dist/assets/antd-img-crop.js +0 -1495
- package/shell-dist/assets/antd-locale-en_US.js +0 -374
- package/shell-dist/assets/antd-locale-zh_CN.js +0 -379
- package/shell-dist/assets/antd.js +0 -106874
- package/shell-dist/assets/clsx.js +0 -33
- package/shell-dist/assets/contract-errors.js +0 -30
- package/shell-dist/assets/contract.js +0 -18900
- package/shell-dist/assets/cssinjs.js +0 -2595
- package/shell-dist/assets/dayjs-plugin-advancedFormat.js +0 -98
- package/shell-dist/assets/dayjs-plugin-customParseFormat.js +0 -173
- package/shell-dist/assets/dayjs-plugin-isoWeek.js +0 -77
- package/shell-dist/assets/dayjs-plugin-localeData.js +0 -126
- package/shell-dist/assets/dayjs-plugin-quarterOfYear.js +0 -77
- package/shell-dist/assets/dayjs-plugin-relativeTime.js +0 -92
- package/shell-dist/assets/dayjs-plugin-weekOfYear.js +0 -73
- package/shell-dist/assets/dayjs-plugin-weekYear.js +0 -63
- package/shell-dist/assets/dayjs-plugin-weekday.js +0 -63
- package/shell-dist/assets/dayjs.js +0 -326
- package/shell-dist/assets/dnd-kit-core.js +0 -3601
- package/shell-dist/assets/dnd-kit-sortable.js +0 -717
- package/shell-dist/assets/dnd-kit-utilities.js +0 -340
- package/shell-dist/assets/i18next.js +0 -2321
- package/shell-dist/assets/icons-es-components-Context.js +0 -20
- package/shell-dist/assets/icons.js +0 -34961
- package/shell-dist/assets/jsx-dev-runtime.js +0 -75
- package/shell-dist/assets/jsx-runtime.js +0 -97
- package/shell-dist/assets/keepalive-for-react.js +0 -283
- package/shell-dist/assets/kit.js +0 -151
- package/shell-dist/assets/ky.js +0 -1427
- package/shell-dist/assets/motion-react.js +0 -13589
- package/shell-dist/assets/motion.js +0 -9959
- package/shell-dist/assets/nprogress.js +0 -314
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-BarsOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CalendarOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretDownFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretDownOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CaretUpOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CheckCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CheckOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ClockCircleOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CloseCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CloseOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-CopyOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DeleteOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DoubleLeftOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DoubleRightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DownOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-DownloadOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EditOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EllipsisOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EnterOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ExclamationCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EyeInvisibleOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-EyeOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FileOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FileTextOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FilterFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FolderOpenOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-FolderOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-HolderOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-InfoCircleFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-LeftOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-LoadingOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-MinusOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-MinusSquareOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PaperClipOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PictureOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PlusOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-PlusSquareOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-QuestionCircleOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ReloadOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-RightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-RotateLeftOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-RotateRightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-SearchOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-StarFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-SwapOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-SwapRightOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-UpOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-VerticalAlignTopOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-WarningFilled.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ZoomInOutlined.js +0 -7
- package/shell-dist/assets/ojm--ant-design-icons-es-icons-ZoomOutOutlined.js +0 -7
- package/shell-dist/assets/ojm-dayjs-locale-zh-cn.js +0 -81
- package/shell-dist/assets/pinyin-pro.js +0 -25175
- package/shell-dist/assets/pro-components.js +0 -44627
- package/shell-dist/assets/react-countup.js +0 -525
- package/shell-dist/assets/react-dom-client.js +0 -12134
- package/shell-dist/assets/react-dom.js +0 -266
- package/shell-dist/assets/react-error-boundary.js +0 -141
- package/shell-dist/assets/react-i18next.js +0 -1773
- package/shell-dist/assets/react-jss.js +0 -4089
- package/shell-dist/assets/react-query.js +0 -3072
- package/shell-dist/assets/react-router-dom.js +0 -9927
- package/shell-dist/assets/react-router.js +0 -14584
- package/shell-dist/assets/react.js +0 -590
- package/shell-dist/assets/runtime.js +0 -28311
- package/shell-dist/assets/simplebar-react.js +0 -1119
- package/shell-dist/assets/spin-delay.js +0 -111
- package/shell-dist/assets/tailwind-merge.js +0 -3364
- package/shell-dist/assets/use-sync-external-store-shim.js +0 -130
- package/shell-dist/assets/zustand-middleware.js +0 -513
- package/shell-dist/assets/zustand-react-shallow.js +0 -27
- package/shell-dist/assets/zustand-react.js +0 -39
- package/shell-dist/assets/zustand-shallow.js +0 -22
- package/shell-dist/assets/zustand-vanilla-shallow.js +0 -71
- package/shell-dist/assets/zustand-vanilla.js +0 -40
- package/shell-dist/assets/zustand.js +0 -32
- package/templates/seed.mysql.sql +0 -6
- package/templates/seed.pg.sql +0 -6
- package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
- package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
- package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
- package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
- package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
- package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
- package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
- package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
- package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
- package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
- package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
- package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
- package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
- package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
- /package/shell-dist/assets/{nprogress-css.js → nprogress-css.1cq2pzkre0.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-button.js → ojm-antd-es-button.2uriphio50.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-modal.js → ojm-antd-es-modal.bf3tgdh8v0.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-slider.js → ojm-antd-es-slider.xyvptuhid0.js} +0 -0
- /package/shell-dist/assets/{ojm-antd-es-upload.js → ojm-antd-es-upload.13gx45e0g0.js} +0 -0
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
useAuthStore,
|
|
7
7
|
useTenantStore,
|
|
8
8
|
} from "@oj-module/runtime";
|
|
9
|
-
import { Alert, Button, Form, Input, Tabs, Typography } from "antd";
|
|
9
|
+
import { Alert, Button, Form, Input, Skeleton, Tabs, Typography } from "antd";
|
|
10
10
|
import { useEffect, useRef, useState } from "react";
|
|
11
11
|
import { useTranslation } from "react-i18next";
|
|
12
12
|
import { useLocation, useNavigate } from "react-router";
|
|
13
|
+
import { getWecomBootError } from "../routes";
|
|
13
14
|
import { parseWecomLanding, wecomAuthorizePath } from "../wecom";
|
|
14
15
|
|
|
15
16
|
type LoginMethod = "local" | "ldap" | "sso" | "wecom";
|
|
@@ -19,7 +20,7 @@ type LoginMethod = "local" | "ldap" | "sso" | "wecom";
|
|
|
19
20
|
* 后端 `methods` 是**发现结果**、这里是**渲染能力**:后端多报一种而本页没有对应
|
|
20
21
|
* renderer 时宁可不显示(点了也没实现),新增方式 = 这里加一项 + 渲染分支加一路。
|
|
21
22
|
*/
|
|
22
|
-
const KNOWN_METHODS: LoginMethod[] = ["
|
|
23
|
+
const KNOWN_METHODS: LoginMethod[] = ["wecom", "local", "ldap", "sso"];
|
|
23
24
|
|
|
24
25
|
function isKnownMethod(x: string): x is LoginMethod {
|
|
25
26
|
return (KNOWN_METHODS as string[]).includes(x);
|
|
@@ -52,12 +53,14 @@ export default function LoginPage() {
|
|
|
52
53
|
const navigate = useNavigate();
|
|
53
54
|
const { search } = useLocation();
|
|
54
55
|
const login = useAuthStore(s => s.login);
|
|
55
|
-
|
|
56
|
+
// onInit 阶段换票若失败,首帧直接显示表单与错误(不再闪「认证中」)
|
|
57
|
+
const bootError = getWecomBootError();
|
|
58
|
+
const [error, setError] = useState<string | undefined>(bootError || undefined);
|
|
56
59
|
const [loading, setLoading] = useState(false);
|
|
57
60
|
const [known, setKnown] = useState(false);
|
|
58
61
|
const inWecom = typeof navigator !== "undefined" && /wxwork/i.test(navigator.userAgent);
|
|
59
62
|
const [methods, setMethods] = useState<LoginMethod[]>(["local"]);
|
|
60
|
-
const [active, setActive] = useState<LoginMethod>("
|
|
63
|
+
const [active, setActive] = useState<LoginMethod>("wecom");
|
|
61
64
|
// 企业代码入口默认收起:单租户部署(绝大多数)保持今天的登录页外观,
|
|
62
65
|
// 只有服务商/客户带 `?tenant=` 或手工展开时才需要填。
|
|
63
66
|
// 例外一:持久化租户不是部署默认值时必须展开——租户头是**隐式**跟着请求走的
|
|
@@ -68,6 +71,8 @@ export default function LoginPage() {
|
|
|
68
71
|
// 判据用「wecom 是否在登录方式里」而不是去数租户:匿名请求没有租户上下文,
|
|
69
72
|
// 统计租户要动用 db.asSystem(全仓仅 tenant-admin.ts 一处,不为此破例)。
|
|
70
73
|
const [showTenant, setShowTenant] = useState(() => useTenantStore.getState().tenant !== DEFAULT_TENANT);
|
|
74
|
+
// 「其他企业」折叠:仅服务商 / 多企业共存部署才需要手填企业代码
|
|
75
|
+
const [showOtherCorp, setShowOtherCorp] = useState(false);
|
|
71
76
|
// 表单里的企业代码:带上 persisted 值(URL `?tenant=` 已先落到 store),用户看得见才改得动
|
|
72
77
|
const [tenant] = useState(() => {
|
|
73
78
|
const current = useTenantStore.getState().tenant;
|
|
@@ -83,29 +88,32 @@ export default function LoginPage() {
|
|
|
83
88
|
|
|
84
89
|
// 企微 302 落地参数:只在首次渲染解析一次(ticket 一次性,解析结果必须稳定)
|
|
85
90
|
const [landing] = useState(() => parseWecomLanding(search));
|
|
86
|
-
const exchangedRef = useRef(false);
|
|
87
91
|
// 仅「认证中 / 已失败」两态:企微内、回跳落地、或 ?corp 深链即进入认证态,
|
|
88
92
|
// 直接展示「认证中」,不渲染登录表单;只有认证失败(换票失败 / 企微方式不可用)才回落表单。
|
|
93
|
+
// 换票成功时 onInit 已把 URL 改到首页,路由首帧就落在 /home,本组件根本不会挂载;bootError 非空即换票失败。
|
|
89
94
|
const [phase, setPhase] = useState<"auth" | "form">(
|
|
90
|
-
inWecom || landing || urlCorp ? "auth" : "form",
|
|
95
|
+
bootError ? "form" : inWecom || landing || urlCorp ? "auth" : "form",
|
|
91
96
|
);
|
|
92
97
|
|
|
93
98
|
useEffect(() => {
|
|
94
99
|
fetchLoginMethods()
|
|
95
100
|
.then((r) => {
|
|
96
101
|
setKnown(true);
|
|
102
|
+
// 站点标题(framework.site_title)经匿名端点回传,登录页即可设置浏览器标签;
|
|
103
|
+
// 其他已登录页保持随菜单(layout-effects 的路由标题),此处仅在登录页生效。
|
|
104
|
+
if (r.site_title)
|
|
105
|
+
document.title = r.site_title;
|
|
97
106
|
// 先按 string[] 看一眼:runtime 的返回类型把 wecom 排除在外,而这里恰恰要认它
|
|
98
107
|
const raw: string[] = r.methods;
|
|
99
|
-
|
|
108
|
+
// 按 KNOWN_METHODS 排序:企微是主入口,排第一个 tab 并默认选中,
|
|
109
|
+
// 「使用企业微信一键登录」首帧即可见,点一次就走。
|
|
110
|
+
const m = raw.filter(isKnownMethod).sort(
|
|
111
|
+
(a, b) => KNOWN_METHODS.indexOf(a) - KNOWN_METHODS.indexOf(b),
|
|
112
|
+
);
|
|
100
113
|
if (!m.length)
|
|
101
114
|
return;
|
|
102
115
|
setMethods(m);
|
|
103
|
-
|
|
104
|
-
// 否则平台/企业两类管理员没法切换登录目标(用户仍可点「收起」手动折叠)。
|
|
105
|
-
if (raw.includes("wecom"))
|
|
106
|
-
setShowTenant(true);
|
|
107
|
-
if (!m.includes("local"))
|
|
108
|
-
setActive(m[0]);
|
|
116
|
+
setActive(m.includes("wecom") ? "wecom" : m[0]);
|
|
109
117
|
})
|
|
110
118
|
.catch(() => {
|
|
111
119
|
setKnown(true);
|
|
@@ -113,28 +121,9 @@ export default function LoginPage() {
|
|
|
113
121
|
});
|
|
114
122
|
}, []);
|
|
115
123
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return;
|
|
120
|
-
exchangedRef.current = true;
|
|
121
|
-
void (async () => {
|
|
122
|
-
setLoading(true);
|
|
123
|
-
setError(undefined);
|
|
124
|
-
try {
|
|
125
|
-
// 租户先落 store:provider 每请求读 getState,服务端会比对「头值 == 票据租户」
|
|
126
|
-
if (landing.tenant)
|
|
127
|
-
useTenantStore.getState().setTenant(landing.tenant);
|
|
128
|
-
await login({ username: "", password: "", method: "wecom", ticket: landing.ticket });
|
|
129
|
-
navigate(getRedirectPath(search), { replace: true });
|
|
130
|
-
}
|
|
131
|
-
catch (e) {
|
|
132
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
133
|
-
setPhase("form");
|
|
134
|
-
setLoading(false);
|
|
135
|
-
}
|
|
136
|
-
})();
|
|
137
|
-
}, [landing, login, navigate, search]);
|
|
124
|
+
// 企微票据换会话已提前到模块 onInit(见 login/routes.ts::exchangeWecomTicketOnBoot):
|
|
125
|
+
// 成功时 URL 已在首帧前改到首页,本页不挂载;失败时 bootError 已在上面落成表单 + 错误态。
|
|
126
|
+
// 这里不再发第二次 POST——票据一次性,重复换只会拿到 401 并盖掉真正的错误。
|
|
138
127
|
|
|
139
128
|
// 企微深链自动入口:带 ?corp= 到达(且非回跳落地 / 非出错)且企微方式可用时,
|
|
140
129
|
// 直接整页发起授权——「点开即登录」,无需手动点「企业微信」tab。
|
|
@@ -142,7 +131,8 @@ export default function LoginPage() {
|
|
|
142
131
|
useEffect(() => {
|
|
143
132
|
const c = new URLSearchParams(search).get("corp")?.trim() ?? "";
|
|
144
133
|
// 企微内(wxwork UA)从「应用主页」点开即自动发起授权;带 ?corp= 的深链同样自动发起。
|
|
145
|
-
|
|
134
|
+
// 换票失败后不再自动重发授权:否则废票 → 重新授权 → 再换一次废票会原地打转。
|
|
135
|
+
if ((!c && !inWecom) || landing || getWecomBootError() || autoFiredRef.current)
|
|
146
136
|
return;
|
|
147
137
|
if (new URLSearchParams(search).get("error")) {
|
|
148
138
|
setPhase("form");
|
|
@@ -185,14 +175,22 @@ export default function LoginPage() {
|
|
|
185
175
|
window.location.assign(`${resolveApiBase()}/auth/sso`);
|
|
186
176
|
}
|
|
187
177
|
|
|
188
|
-
|
|
178
|
+
/**
|
|
179
|
+
* 一键扫码登录:默认不传 corp/agent,后端 `corpSelf()` 从部署常量 `wecom_corp_*`
|
|
180
|
+
* 自动解析本企业(api.ts::login → resolveWecomProfile),用户不必填企业代码。
|
|
181
|
+
* 普通浏览器 UA 下后端 302 到企微 qrConnect 扫码页;企微内 UA 走 oauth2 静默。
|
|
182
|
+
* 只有「其他企业」折叠里才带 corp/agent(服务商 / 多企业共存部署)。
|
|
183
|
+
*/
|
|
184
|
+
function wecomLogin(withCorp = false) {
|
|
189
185
|
// 整页跳转(拿不到 302 的 opaque redirect):授权入口是**后端端点**,
|
|
190
186
|
// 必须拼 API base(部署期可为 /api 或网关前缀);拼站点 base 只会落到
|
|
191
187
|
// SPA 兜底页——点击后页面原地重载、一个 auth 请求都不发。
|
|
192
188
|
// 企业代码不在这里写 store——只有服务端回跳带的 tenant 才是权威值。
|
|
193
|
-
window.location.assign(`${resolveApiBase()}${wecomAuthorizePath(corp, agent)}`);
|
|
189
|
+
window.location.assign(`${resolveApiBase()}${wecomAuthorizePath(withCorp ? corp : "", withCorp ? agent : "")}`);
|
|
194
190
|
}
|
|
195
191
|
|
|
192
|
+
// 所有登录方式收进同一组 Tabs,同一时刻只渲染当前面板;默认选中企微(见上方 effect),
|
|
193
|
+
// 一键按钮首帧即可见、点一次就走,账号密码收在另一个 tab,不再上下平铺。
|
|
196
194
|
const showTabs = methods.length > 1;
|
|
197
195
|
|
|
198
196
|
const form = (method: LoginMethod) => (
|
|
@@ -222,50 +220,40 @@ export default function LoginPage() {
|
|
|
222
220
|
</Form>
|
|
223
221
|
);
|
|
224
222
|
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
// 少了这层关联,标签只是视觉文本,读屏与 getByLabel 都取不到输入框。
|
|
223
|
+
// 企微一键扫码:默认可见、不藏在 tab 里,点一次即进入企微授权(浏览器扫码 / 企微内静默)。
|
|
224
|
+
// 企业代码 / 应用 ID 收进折叠,只服务服务商多企业场景;单企业部署用户什么都不用填。
|
|
228
225
|
const wecomCorpId = "wecom-corp";
|
|
229
226
|
const wecomAgentId = "wecom-agent";
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
<Input
|
|
238
|
-
id={wecomCorpId}
|
|
239
|
-
autoComplete="off"
|
|
240
|
-
placeholder="ww..."
|
|
241
|
-
value={corp}
|
|
242
|
-
onChange={e => setCorp(e.target.value)}
|
|
243
|
-
/>
|
|
244
|
-
</Form.Item>
|
|
245
|
-
<Form.Item
|
|
246
|
-
label={t("login:method.wecom.agent")}
|
|
247
|
-
htmlFor={wecomAgentId}
|
|
248
|
-
extra={t("login:method.wecom.agentHint")}
|
|
249
|
-
>
|
|
250
|
-
<Input
|
|
251
|
-
id={wecomAgentId}
|
|
252
|
-
autoComplete="off"
|
|
253
|
-
placeholder="1000..."
|
|
254
|
-
value={agent}
|
|
255
|
-
onChange={e => setAgent(e.target.value)}
|
|
256
|
-
/>
|
|
257
|
-
</Form.Item>
|
|
258
|
-
<Button type="primary" htmlType="submit" block>
|
|
259
|
-
{t("login:method.wecom.action")}
|
|
227
|
+
const wecomQuickPanel = (
|
|
228
|
+
<>
|
|
229
|
+
<Typography.Paragraph type="secondary">
|
|
230
|
+
{t("login:method.wecom.scan.hint")}
|
|
231
|
+
</Typography.Paragraph>
|
|
232
|
+
<Button type="primary" block size="large" onClick={() => wecomLogin()}>
|
|
233
|
+
{t("login:method.wecom.scan.action")}
|
|
260
234
|
</Button>
|
|
261
|
-
|
|
235
|
+
<Button type="link" size="small" block onClick={() => setShowOtherCorp(v => !v)}>
|
|
236
|
+
{t(showOtherCorp ? "login:method.wecom.otherCorpHide" : "login:method.wecom.otherCorp")}
|
|
237
|
+
</Button>
|
|
238
|
+
{showOtherCorp && (
|
|
239
|
+
<Form layout="vertical" onFinish={() => wecomLogin(true)} style={{ marginTop: 8 }}>
|
|
240
|
+
<Form.Item label={t("login:page.tenant.label")} htmlFor={wecomCorpId} extra={t("login:method.wecom.hint")}>
|
|
241
|
+
<Input id={wecomCorpId} autoComplete="off" placeholder="ww..." value={corp} onChange={e => setCorp(e.target.value)} />
|
|
242
|
+
</Form.Item>
|
|
243
|
+
<Form.Item label={t("login:method.wecom.agent")} htmlFor={wecomAgentId} extra={t("login:method.wecom.agentHint")}>
|
|
244
|
+
<Input id={wecomAgentId} autoComplete="off" placeholder="1000..." value={agent} onChange={e => setAgent(e.target.value)} />
|
|
245
|
+
</Form.Item>
|
|
246
|
+
<Button htmlType="submit" block>{t("login:method.wecom.action")}</Button>
|
|
247
|
+
</Form>
|
|
248
|
+
)}
|
|
249
|
+
</>
|
|
262
250
|
);
|
|
263
251
|
|
|
264
252
|
function panelOf(m: LoginMethod) {
|
|
265
253
|
if (m === "sso")
|
|
266
254
|
return <Button type="primary" block onClick={ssoLogin}>{t("login:method.sso.action")}</Button>;
|
|
267
255
|
if (m === "wecom")
|
|
268
|
-
return
|
|
256
|
+
return wecomQuickPanel;
|
|
269
257
|
return form(m);
|
|
270
258
|
}
|
|
271
259
|
|
|
@@ -290,19 +278,24 @@ export default function LoginPage() {
|
|
|
290
278
|
{t("login:page.subtitle")}
|
|
291
279
|
</Typography.Paragraph>
|
|
292
280
|
{error && <Alert style={{ marginBottom: 16 }} type="error" message={error} showIcon />}
|
|
293
|
-
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
281
|
+
{/* 登录方式发现完成前不渲染表单区:否则首帧先画账号密码、再切成默认企微 tab,
|
|
282
|
+
工作台用户会看到一次无谓的闪动(见 8b06d4e1 的闪屏回归)。 */}
|
|
283
|
+
{!known
|
|
284
|
+
? <Skeleton active paragraph={{ rows: 4 }} />
|
|
285
|
+
: showTabs
|
|
286
|
+
? (
|
|
287
|
+
<Tabs
|
|
288
|
+
destroyInactiveTabPane
|
|
289
|
+
activeKey={active}
|
|
290
|
+
onChange={key => setActive(key as LoginMethod)}
|
|
291
|
+
items={methods.map(m => ({
|
|
292
|
+
key: m,
|
|
293
|
+
label: t(`login:method.${m}`),
|
|
294
|
+
children: panelOf(m),
|
|
295
|
+
}))}
|
|
296
|
+
/>
|
|
297
|
+
)
|
|
298
|
+
: panelOf(methods[0] ?? "local")}
|
|
306
299
|
</>
|
|
307
300
|
);
|
|
308
301
|
}
|
|
@@ -1,9 +1,59 @@
|
|
|
1
1
|
import type { AppRouteRecordRaw, ModuleContext } from "@oj-module/runtime";
|
|
2
2
|
|
|
3
|
-
import { useAuthStore, useTenantStore } from "@oj-module/runtime";
|
|
3
|
+
import { getRedirectPath, resolveSiteBase, useAuthStore, useTenantStore } from "@oj-module/runtime";
|
|
4
4
|
import { lazy } from "react";
|
|
5
5
|
|
|
6
6
|
import { authProvider } from "./auth-provider";
|
|
7
|
+
import { parseWecomLanding } from "./wecom";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* onInit 阶段「企微票据换会话」失败的原因。登录页首帧读一次:非空就直接显示表单与错误,
|
|
11
|
+
* 不再进「认证中」态,也不再自动重发授权(避免废票 → 重授权 → 再失败的循环)。
|
|
12
|
+
*/
|
|
13
|
+
let wecomBootError = "";
|
|
14
|
+
|
|
15
|
+
export function getWecomBootError(): string {
|
|
16
|
+
return wecomBootError;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 企微回跳落地(`/login?wecom_ticket=…`)的换票**提前到首帧 render 之前**。
|
|
21
|
+
*
|
|
22
|
+
* 原来放在 LoginPage 的 useEffect 里:路由先把登录页(全屏外壳 + 品牌位 + 「认证中」)
|
|
23
|
+
* 画出来,等一个 POST 换完票、token 落 store 后才 Navigate 到首页——工作台进入时
|
|
24
|
+
* 登录页明显闪一下。模块 onInit 在宿主建路由之前被 await,在这里换完票即把 URL 改成
|
|
25
|
+
* 首页(见下方 shell 无 AuthGuard 的说明),路由首帧就落在 /home,登录页一次都不挂载。
|
|
26
|
+
*
|
|
27
|
+
* 失败不阻断启动:保持 /login,错误记下来交给登录页首帧显示表单与错误,用户可改用
|
|
28
|
+
* 本地登录 / 重新授权;一次性票据在成功改 URL 时一并从地址栏与历史里抹掉。
|
|
29
|
+
*/
|
|
30
|
+
async function exchangeWecomTicketOnBoot(): Promise<void> {
|
|
31
|
+
const landing = parseWecomLanding(window.location.search);
|
|
32
|
+
if (!landing)
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
// 租户由 tenant.ts::bootstrapTenant 在模块作用域从 ?tenant= 落地;这里补一次,
|
|
36
|
+
// 让换票请求的 X-TENANT-ID 与票据租户严格一致(服务端会比对两者)。
|
|
37
|
+
if (landing.tenant)
|
|
38
|
+
useTenantStore.getState().setTenant(landing.tenant);
|
|
39
|
+
await useAuthStore.getState().login({
|
|
40
|
+
username: "",
|
|
41
|
+
password: "",
|
|
42
|
+
method: "wecom",
|
|
43
|
+
ticket: landing.ticket,
|
|
44
|
+
});
|
|
45
|
+
// 宿主 shell 链**没有 AuthGuard**(免登录演示能力绕开 LayoutRoot),所以「已登录」
|
|
46
|
+
// 不会自动把 /login 跳走。宿主在 loadAll(即本 onInit)之后才 createBrowserRouter,
|
|
47
|
+
// 此刻先把 URL 换成首页,路由首帧就落在 /home,登录页一次都不挂载。
|
|
48
|
+
// runtime 自有链带 AuthGuard,这处 replace 也无害(守卫同样跳首页)。
|
|
49
|
+
const base = resolveSiteBase().replace(/\/+$/, "");
|
|
50
|
+
window.history.replaceState(null, "", `${base}${getRedirectPath(window.location.search)}`);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// 失败保持 /login:bootError 交给登录页首帧显示表单与错误,不再闪「认证中」
|
|
54
|
+
wecomBootError = e instanceof Error ? e.message : String(e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
7
57
|
|
|
8
58
|
/**
|
|
9
59
|
* 登录根 `/login`(+ 强制改密 `/change-password`)。
|
|
@@ -68,4 +118,7 @@ export async function onInit(ctx: ModuleContext) {
|
|
|
68
118
|
if (prev.token && !state.token)
|
|
69
119
|
useTenantStore.getState().clear();
|
|
70
120
|
});
|
|
121
|
+
// 必须在 provider 注册之后、首帧 render 之前:企微回跳的票据在这里换完,
|
|
122
|
+
// 登录页就不会先闪一下再跳首页(见 exchangeWecomTicketOnBoot 注释)。
|
|
123
|
+
await exchangeWecomTicketOnBoot();
|
|
71
124
|
}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"roles": "Roles & Permissions",
|
|
7
7
|
"oplog": "Audit Log",
|
|
8
8
|
"settings": "Site Settings",
|
|
9
|
-
"loginMethods": "Login Methods",
|
|
10
9
|
"wecom": "WeCom Integration",
|
|
11
10
|
"departments": "Departments"
|
|
12
11
|
},
|
|
@@ -130,13 +129,6 @@
|
|
|
130
129
|
"secretHint": "Secret items cannot be edited online",
|
|
131
130
|
"saved": "Settings saved"
|
|
132
131
|
},
|
|
133
|
-
"loginMethodsPage": {
|
|
134
|
-
"title": "Enabled login methods",
|
|
135
|
-
"methodLocal": "Local password",
|
|
136
|
-
"methodLdap": "LDAP",
|
|
137
|
-
"methodSso": "SSO",
|
|
138
|
-
"methodWecom": "WeCom"
|
|
139
|
-
},
|
|
140
132
|
"wecom": {
|
|
141
133
|
"title": "Onboard a corporation",
|
|
142
134
|
"hint": "Click \"Generate install link\" and send the link or QR code to the corporation's admin to scan/open for approval. On success this system auto-registers the corporation via the create_auth callback (writes wecom_app_profile). The link is generated by WeCom get_customized_auth_url; its validity follows WeCom's response (up to 10 days). The QR code greys out when it expires, so regenerate it.",
|
|
@@ -276,6 +268,7 @@
|
|
|
276
268
|
"login": "Login Methods",
|
|
277
269
|
"ldap": "LDAP"
|
|
278
270
|
},
|
|
271
|
+
"loginMethods": "Login Methods",
|
|
279
272
|
"site_title": "Site Title",
|
|
280
273
|
"market_cdn_url": "Market CDN URL",
|
|
281
274
|
"ldap_enabled": "Enable LDAP login",
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"roles": "角色权限",
|
|
7
7
|
"oplog": "操作日志",
|
|
8
8
|
"settings": "站点设置",
|
|
9
|
-
"loginMethods": "登录方式",
|
|
10
9
|
"wecom": "企微集成",
|
|
11
10
|
"departments": "组织架构"
|
|
12
11
|
},
|
|
@@ -130,13 +129,6 @@
|
|
|
130
129
|
"secretHint": "敏感项不可在线修改",
|
|
131
130
|
"saved": "设置已保存"
|
|
132
131
|
},
|
|
133
|
-
"loginMethodsPage": {
|
|
134
|
-
"title": "当前启用的登录方式",
|
|
135
|
-
"methodLocal": "本地口令",
|
|
136
|
-
"methodLdap": "LDAP",
|
|
137
|
-
"methodSso": "SSO 单点登录",
|
|
138
|
-
"methodWecom": "企业微信"
|
|
139
|
-
},
|
|
140
132
|
"wecom": {
|
|
141
133
|
"title": "开通企业",
|
|
142
134
|
"hint": "点「生成开通链接」把链接或二维码发给企业管理员,由其扫码/打开授权;授权成功后本系统经 create_auth 回调自动登记该企业(落库 wecom_app_profile)。链接由企微 get_customized_auth_url 生成,有效期以企微返回为准(可达 10 天),过期后二维码置灰,可重新生成。",
|
|
@@ -276,6 +268,7 @@
|
|
|
276
268
|
"login": "登录方式",
|
|
277
269
|
"ldap": "LDAP"
|
|
278
270
|
},
|
|
271
|
+
"loginMethods": "登录方式",
|
|
279
272
|
"site_title": "站点标题",
|
|
280
273
|
"market_cdn_url": "市场 CDN 地址",
|
|
281
274
|
"ldap_enabled": "启用 LDAP 登录",
|
|
@@ -3,18 +3,21 @@ import type { TFunction } from "i18next";
|
|
|
3
3
|
import type { SystemSettingsGetData } from "../../__client";
|
|
4
4
|
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import { App, Button, Card, Input, Space, Switch, Tooltip } from "antd";
|
|
6
|
+
import { App, Button, Card, Input, Space, Switch, Tag, Tooltip } from "antd";
|
|
7
7
|
import { useState } from "react";
|
|
8
8
|
import { useTranslation } from "react-i18next";
|
|
9
9
|
|
|
10
|
-
import { systemSettingsGet, systemSettingsPut } from "../../__client";
|
|
10
|
+
import { systemLoginMethodsGet, systemSettingsGet, systemSettingsPut } from "../../__client";
|
|
11
11
|
import { explainError, isForbidden } from "../errors";
|
|
12
12
|
import { ForbiddenPanel } from "../forbidden";
|
|
13
13
|
|
|
14
14
|
type SettingItem = SystemSettingsGetData[number]["items"][number];
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 只动框架键(规格 §3):按 module 字段过滤,业务自定义键(如 wiki 的 mw_config)与 market.*(security 组,super-only 写)不进入。
|
|
18
|
+
* 登录方式(ldap/sso/wecom)不再作为可编辑设置,收敛为下方只读卡片(与 /login 同源)。
|
|
19
|
+
*/
|
|
20
|
+
const FRAMEWORK_MODULES = new Set(["framework"]);
|
|
18
21
|
|
|
19
22
|
/** 单项行:bool → Switch("1"/"0");secret → 只读 ***;其余 Input 失焦/回车保存 */
|
|
20
23
|
function SettingRow({ item, t, onSave }: { item: SettingItem, t: TFunction, onSave: (key: string, value: string) => void }) {
|
|
@@ -73,6 +76,7 @@ export default function SettingsPage() {
|
|
|
73
76
|
const queryClient = useQueryClient();
|
|
74
77
|
|
|
75
78
|
const query = useQuery({ queryKey: ["system", "settings"], queryFn: systemSettingsGet });
|
|
79
|
+
const loginMethodsQuery = useQuery({ queryKey: ["system", "login-methods"], queryFn: systemLoginMethodsGet });
|
|
76
80
|
|
|
77
81
|
const putMut = useMutation({
|
|
78
82
|
mutationFn: (body: { key: string, value: string }) => systemSettingsPut(body),
|
|
@@ -90,8 +94,17 @@ export default function SettingsPage() {
|
|
|
90
94
|
.map(g => ({ ...g, items: g.items.filter(i => FRAMEWORK_MODULES.has(i.module)) }))
|
|
91
95
|
.filter(g => g.items.length > 0);
|
|
92
96
|
|
|
97
|
+
const loginMethods = loginMethodsQuery.data?.methods ?? ["local"];
|
|
98
|
+
|
|
93
99
|
return (
|
|
94
100
|
<Space direction="vertical" style={{ width: "100%", paddingTop: 16 }}>
|
|
101
|
+
<Card title={t("system:setting.loginMethods")}>
|
|
102
|
+
<Space wrap>
|
|
103
|
+
{loginMethods.map(m => (
|
|
104
|
+
<Tag key={m}>{t(`login:method.${m}`, { defaultValue: m })}</Tag>
|
|
105
|
+
))}
|
|
106
|
+
</Space>
|
|
107
|
+
</Card>
|
|
95
108
|
{groups.map(g => (
|
|
96
109
|
<Card key={g.group} title={t(`system:setting.group.${g.group}`, { defaultValue: g.group })}>
|
|
97
110
|
<Space direction="vertical" style={{ width: "100%" }}>
|
|
@@ -12,7 +12,6 @@ const Departments = lazy(() => import("./pages/departments"));
|
|
|
12
12
|
const Roles = lazy(() => import("./pages/roles"));
|
|
13
13
|
const Oplog = lazy(() => import("./pages/oplog"));
|
|
14
14
|
const Settings = lazy(() => import("./pages/settings"));
|
|
15
|
-
const LoginMethods = lazy(() => import("./pages/login-methods"));
|
|
16
15
|
const WecomAdmin = lazy(() => import("./pages/wecom"));
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -76,11 +75,6 @@ export const routes: AppRouteRecordRaw[] = [
|
|
|
76
75
|
Component: Settings,
|
|
77
76
|
handle: { title: "system:menu.settings", roles: ["superadmin"] },
|
|
78
77
|
},
|
|
79
|
-
{
|
|
80
|
-
path: "/system/login-methods",
|
|
81
|
-
Component: LoginMethods,
|
|
82
|
-
handle: { title: "system:menu.loginMethods" },
|
|
83
|
-
},
|
|
84
78
|
{
|
|
85
79
|
// 企微代开发后台(服务商运营面):仅超管可见(路由级 roles + 页面内二次判定)
|
|
86
80
|
path: "/system/wecom",
|
|
@@ -73,6 +73,39 @@ describe("cardGrid 卡片网格(BDD)", () => {
|
|
|
73
73
|
expect(screen.queryByTestId("card-license-alpha")).toBeNull();
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
it("given 索引带 summary/description When 渲染 Then 卡片显示简介与描述(描述全文进 title 供查看)", () => {
|
|
77
|
+
const description = "详细描述:提供示例 CRUD、菜单路由与安装/升级/回滚全链路。";
|
|
78
|
+
renderWithProviders(
|
|
79
|
+
<CardGrid rows={[{ ...installed, summary: "一句话说清方案做什么", description }]} {...handlers} />,
|
|
80
|
+
new InMemoryMarketClient(),
|
|
81
|
+
);
|
|
82
|
+
expect(screen.getByTestId("card-summary-alpha").textContent).toContain("一句话说清方案做什么");
|
|
83
|
+
const desc = screen.getByTestId("card-description-alpha");
|
|
84
|
+
expect(desc.textContent).toContain(description);
|
|
85
|
+
expect(desc.getAttribute("title")).toBe(description);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("given 索引未声明 summary/description When 渲染 Then 卡片不出现简介/描述块(不显示占位符)", () => {
|
|
89
|
+
renderWithProviders(<CardGrid rows={[installed]} {...handlers} />, new InMemoryMarketClient());
|
|
90
|
+
expect(screen.queryByTestId("card-summary-alpha")).toBeNull();
|
|
91
|
+
expect(screen.queryByTestId("card-description-alpha")).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("given 索引带 publisher_key When 渲染 Then 卡片显示发布者(签出的 key_id,全文进 title)", () => {
|
|
95
|
+
renderWithProviders(
|
|
96
|
+
<CardGrid rows={[{ ...installed, publisher_key: "demo-solution.wecom-everpan" }]} {...handlers} />,
|
|
97
|
+
new InMemoryMarketClient(),
|
|
98
|
+
);
|
|
99
|
+
const pub = screen.getByTestId("card-publisher-alpha");
|
|
100
|
+
expect(pub.textContent).toContain("demo-solution.wecom-everpan");
|
|
101
|
+
expect(pub.getAttribute("title")).toBe("demo-solution.wecom-everpan");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("given 索引未声明 publisher_key When 渲染 Then 卡片不出现发布者行(不显示占位符)", () => {
|
|
105
|
+
renderWithProviders(<CardGrid rows={[installed]} {...handlers} />, new InMemoryMarketClient());
|
|
106
|
+
expect(screen.queryByTestId("card-publisher-alpha")).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
76
109
|
it("given 未安装卡片 When 渲染 Then 安装按钮显示「安装」而非「升级」,且无回滚按钮", () => {
|
|
77
110
|
renderWithProviders(<CardGrid rows={[notInstalled]} {...handlers} />, new InMemoryMarketClient());
|
|
78
111
|
expect(screen.getByText("安装")).toBeTruthy();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { IndexRow } from "../types";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { DetailDrawer } from "../components/DetailDrawer";
|
|
5
|
+
import { InMemoryMarketClient } from "./marketClient.fake";
|
|
6
|
+
import { renderWithProviders } from "./render";
|
|
7
|
+
|
|
8
|
+
const row: IndexRow = {
|
|
9
|
+
name: "alpha",
|
|
10
|
+
latest: "1.2.0",
|
|
11
|
+
installed: "1.1.0",
|
|
12
|
+
enabled: true,
|
|
13
|
+
upgradable: true,
|
|
14
|
+
summary: "一站式客户管理",
|
|
15
|
+
description: "第一行:客户档案\n第二行:跟进记录与报表",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe("detailDrawer 方案详情(BDD)", () => {
|
|
19
|
+
const handlers = {
|
|
20
|
+
onClose: vi.fn(),
|
|
21
|
+
onInstall: vi.fn(),
|
|
22
|
+
onHistory: vi.fn(),
|
|
23
|
+
onRollback: vi.fn(),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
it("given 带 summary/description When 打开 Then 展示完整简介与描述(描述保留换行,卡片限行的全文在此)", () => {
|
|
27
|
+
renderWithProviders(
|
|
28
|
+
<DetailDrawer row={row} open canManage {...handlers} />,
|
|
29
|
+
new InMemoryMarketClient(),
|
|
30
|
+
);
|
|
31
|
+
expect(screen.getByText("简介")).toBeTruthy();
|
|
32
|
+
expect(screen.getByText("详细介绍")).toBeTruthy();
|
|
33
|
+
expect(screen.getByTestId("detail-summary").textContent).toBe(row.summary);
|
|
34
|
+
const desc = screen.getByTestId("detail-description");
|
|
35
|
+
expect(desc.textContent).toBe(row.description);
|
|
36
|
+
expect(desc.style.whiteSpace).toBe("pre-wrap");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("given 未声明 summary/description When 打开 Then 不出现这两行(不显示占位符)", () => {
|
|
40
|
+
renderWithProviders(
|
|
41
|
+
<DetailDrawer row={{ ...row, summary: undefined, description: undefined }} open canManage {...handlers} />,
|
|
42
|
+
new InMemoryMarketClient(),
|
|
43
|
+
);
|
|
44
|
+
expect(screen.queryByTestId("detail-summary")).toBeNull();
|
|
45
|
+
expect(screen.queryByTestId("detail-description")).toBeNull();
|
|
46
|
+
expect(screen.queryByText("简介")).toBeNull();
|
|
47
|
+
expect(screen.queryByText("详细介绍")).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("given 带 publisher_key When 打开 Then 详情展示发布者(签出的 key_id,长串可折行)", () => {
|
|
51
|
+
renderWithProviders(
|
|
52
|
+
<DetailDrawer row={{ ...row, publisher_key: "demo-solution.wecom-everpan.macbook" }} open canManage {...handlers} />,
|
|
53
|
+
new InMemoryMarketClient(),
|
|
54
|
+
);
|
|
55
|
+
expect(screen.getByText("发布者")).toBeTruthy();
|
|
56
|
+
const pub = screen.getByTestId("detail-publisher");
|
|
57
|
+
expect(pub.textContent).toBe("demo-solution.wecom-everpan.macbook");
|
|
58
|
+
expect(pub.style.wordBreak).toBe("break-all");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("given 未声明 publisher_key When 打开 Then 不出现发布者行(不显示占位符)", () => {
|
|
62
|
+
renderWithProviders(
|
|
63
|
+
<DetailDrawer row={{ ...row, publisher_key: undefined }} open canManage {...handlers} />,
|
|
64
|
+
new InMemoryMarketClient(),
|
|
65
|
+
);
|
|
66
|
+
expect(screen.queryByTestId("detail-publisher")).toBeNull();
|
|
67
|
+
expect(screen.queryByText("发布者")).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { MarketClient } from "../api/client";
|
|
2
2
|
// 内存实现 MarketClient(BDD 测试注入用,无需真实后端)。
|
|
3
|
-
import type { EnableInput, IndexRow, InstallInput, MarketHistory, TaskRecord, UninstallImpact } from "../types";
|
|
3
|
+
import type { EnableInput, IndexRow, InstallInput, MarketHistory, ReleasePage, TaskRecord, UninstallImpact } from "../types";
|
|
4
4
|
|
|
5
5
|
export interface FakeState {
|
|
6
6
|
indexRows?: IndexRow[]
|
|
7
7
|
indexError?: Error
|
|
8
8
|
taskRecord?: TaskRecord
|
|
9
9
|
historyRows?: MarketHistory[]
|
|
10
|
+
releasePage?: ReleasePage
|
|
10
11
|
impact?: UninstallImpact
|
|
11
12
|
enableResult?: { ok: boolean, message: string }
|
|
12
13
|
uninstallResult?: { ok: boolean, message: string }
|
|
@@ -65,6 +66,11 @@ export class InMemoryMarketClient implements MarketClient {
|
|
|
65
66
|
return this.state.historyRows ?? [];
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
async releases(input: { page?: number, limit?: number } = {}) {
|
|
70
|
+
this.calls.push(`releases:${input.page ?? 1}:${input.limit ?? 20}`);
|
|
71
|
+
return this.state.releasePage ?? { list: [], total: 0, page: 1, size: 20, syncError: null };
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
async uninstallImpact(name: string) {
|
|
69
75
|
this.calls.push(`uninstallImpact:${name}`);
|
|
70
76
|
return this.state.impact ?? { name, affectedRoles: 0, affectedUsers: 0 };
|