diff --git a/template/src/components/login/byOauth/ProviderList/index.ts b/template/src/components/login/byOauth/ProviderList/index.ts new file mode 100644 index 000000000..1d3a2cc62 --- /dev/null +++ b/template/src/components/login/byOauth/ProviderList/index.ts @@ -0,0 +1,62 @@ +import { EntityDict } from "@project/oak-app-domain"; +import assert from "assert"; +import { RowWithActions } from "oak-frontend-base"; +import { Cache } from "@oak-frontend-base/features/cache"; +import { generateNewId } from "oak-domain/lib/utils/uuid"; +import { Token } from "@oak-general-business/features/token"; +import { FeatureDict } from "@project/features"; + +export default OakComponent({ + entity: 'oauthProvider', + isList: true, + projection: { + name: 1, + type: 1, + logo: 1, + authorizationEndpoint: 1, + redirectUri: 1, + clientId: 1, + }, + filters: [{ + filter() { + const application = this.features.application.getApplication(); + return { + systemId: application?.systemId!, + ableState: "enabled", + }; + }, + }], + formData({ data }) { + return { + list: data, + }; + }, + data: { + loading: false + }, + properties: {}, + methods: { + onLogin(provider: RowWithActions) { + this.setLoading(true); + + const { name, type, redirectUri, clientId } = provider; + assert(redirectUri, 'Redirect URI is required for OAuth login'); + assert(clientId, 'Client ID is required for OAuth login'); + + this.features.aspect.createOAuthState({ + providerId: provider.id!, + type: "login", + userId: this.features.token.getUserId(true), + }).then(state => { + const target = `${provider.authorizationEndpoint}?response_type=code&client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri!)}&state=${state}`; + // 跳转第三方认证页面 + window.location.href = target; + }) + + console.log('Logging in with provider:', provider); + }, + setLoading(loading: boolean) { + this.setState({ loading }); + } + } +}); diff --git a/template/src/components/login/byOauth/ProviderList/locales/zh_CN.json b/template/src/components/login/byOauth/ProviderList/locales/zh_CN.json new file mode 100644 index 000000000..4ed318bc6 --- /dev/null +++ b/template/src/components/login/byOauth/ProviderList/locales/zh_CN.json @@ -0,0 +1,3 @@ +{ + "waiting": "等待中..." +} \ No newline at end of file diff --git a/template/src/components/login/byOauth/ProviderList/styles.module.less b/template/src/components/login/byOauth/ProviderList/styles.module.less new file mode 100644 index 000000000..c7aaa019f --- /dev/null +++ b/template/src/components/login/byOauth/ProviderList/styles.module.less @@ -0,0 +1,152 @@ +.providerContainer { + width: 100%; + margin-top: 24px; + padding: 20px 0; +} + +.loadingWrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 0; +} + +.loadingDots { + display: flex; + gap: 8px; + margin-bottom: 16px; + + span { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #1890ff; + animation: loadingDot 1.4s infinite ease-in-out both; + + &:nth-child(1) { + animation-delay: -0.32s; + } + + &:nth-child(2) { + animation-delay: -0.16s; + } + } +} + +@keyframes loadingDot { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.3; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +.loadingText { + font-size: 14px; + color: rgba(0, 0, 0, 0.45); +} + +.title { + font-size: 14px; + color: rgba(0, 0, 0, 0.45); + text-align: center; + margin-bottom: 24px; + position: relative; + + &::before, + &::after { + content: ''; + position: absolute; + top: 50%; + width: 30%; + height: 1px; + background-color: rgba(0, 0, 0, 0.1); + } + + &::before { + left: 0; + } + + &::after { + right: 0; + } +} + +.providerList { + display: flex; + justify-content: center; + align-items: center; + gap: 32px; + flex-wrap: wrap; +} + +.providerItem { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-4px); + + .logoWrapper { + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); + } + } +} + +.logoWrapper { + width: 56px; + height: 56px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #e8e8e8; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + overflow: hidden; + margin-bottom: 8px; + + &:hover { + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); + } +} + +.logo { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 50%; +} + +.logoPlaceholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + font-weight: 600; + color: #fff; +} + +.providerName { + font-size: 12px; + color: rgba(0, 0, 0, 0.65); + text-align: center; + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: color 0.3s ease; + + .providerItem:hover & { + color: #1890ff; + } +} \ No newline at end of file diff --git a/template/src/components/login/byOauth/ProviderList/web.pc.tsx b/template/src/components/login/byOauth/ProviderList/web.pc.tsx new file mode 100644 index 000000000..145f2b00a --- /dev/null +++ b/template/src/components/login/byOauth/ProviderList/web.pc.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { EntityDict } from '@project/oak-app-domain'; +import { RowWithActions, WebComponentProps } from 'oak-frontend-base'; +import { Tooltip } from 'antd'; +import Styles from './styles.module.less'; + +const ProviderList = ( + props: WebComponentProps< + EntityDict, + 'oauthProvider', + true, + { + list: RowWithActions[]; + loading: boolean; + }, + { + onLogin: (provider: RowWithActions) => void + } + > +) => { + const { list, loading } = props.data; + const { t, onLogin } = props.methods; + + if (loading) { + return ( +
+
+
+ + + +
+
{t("waiting")}
+
+
+ ); + } + + return ( +
+ {list && list.length > 0 && ( + <> +
第三方登录
+
+ {list.map((item) => { + return ( + +
onLogin(item)} + > +
+ {item.logo ? ( + {item.name} + ) : ( +
+ {item.name?.charAt(0).toUpperCase()} +
+ )} +
+
{item.name}
+
+
+ ); + })} +
+ + )} +
+ ); +}; + +export default ProviderList; \ No newline at end of file diff --git a/template/src/components/login/byOauth/index.ts b/template/src/components/login/byOauth/index.ts new file mode 100644 index 000000000..c0a0c3cc3 --- /dev/null +++ b/template/src/components/login/byOauth/index.ts @@ -0,0 +1,6 @@ +export default OakComponent({ + // Virtual Component + isList: false, + filters: [], + properties:{} +}); diff --git a/template/src/components/login/byOauth/locales/zh_CN.json b/template/src/components/login/byOauth/locales/zh_CN.json new file mode 100644 index 000000000..544b7b4dd --- /dev/null +++ b/template/src/components/login/byOauth/locales/zh_CN.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/template/src/components/login/byOauth/styles.module.less b/template/src/components/login/byOauth/styles.module.less new file mode 100644 index 000000000..b13913541 --- /dev/null +++ b/template/src/components/login/byOauth/styles.module.less @@ -0,0 +1,7 @@ +.id { + font-size: 18px; +} + +.item { + font-size: 18px; +} \ No newline at end of file diff --git a/template/src/components/login/byOauth/web.pc.tsx b/template/src/components/login/byOauth/web.pc.tsx new file mode 100644 index 000000000..e1bcf7df6 --- /dev/null +++ b/template/src/components/login/byOauth/web.pc.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { EntityDict } from '@project/oak-app-domain'; +import { RowWithActions, WebComponentProps } from 'oak-frontend-base'; +import Styles from './styles.module.less'; +import ProviderList from './ProviderList'; + +const ByOauth = ( + props: WebComponentProps< + EntityDict, + keyof EntityDict, + false, + { + // virtual + } + > +) => { + const { oakFullpath } = props.data; + + return (); +}; + +export default ByOauth; \ No newline at end of file diff --git a/template/src/components/login/index.ts b/template/src/components/login/index.ts index 65cb22e0e..b4ac8fbdd 100644 --- a/template/src/components/login/index.ts +++ b/template/src/components/login/index.ts @@ -11,12 +11,25 @@ export default OakComponent({ onLoggedInMp() { const { onLoggedIn } = this.props; onLoggedIn && onLoggedIn(); - } + }, + dispose : null as (() => void) | null, }, methods: { logout() { return this.features.token.logout(); } }, + lifetimes: { + ready() { + const dispose = this.features.token.subscribe(() => { + this.props.onLoggedIn && this.props.onLoggedIn(); + }); + + this.setState({ dispose }); + }, + detached() { + this.state.dispose && this.state.dispose(); + } + }, features: ['token'], }); \ No newline at end of file diff --git a/template/src/components/login/web.pc.module.less b/template/src/components/login/web.pc.module.less index 74ea66251..1f4331d83 100644 --- a/template/src/components/login/web.pc.module.less +++ b/template/src/components/login/web.pc.module.less @@ -3,4 +3,10 @@ display: flex; align-items: center; justify-content: center; +} + +.login { + display: flex; + flex-direction: column; + gap: 32px; } \ No newline at end of file diff --git a/template/src/components/login/web.pc.tsx b/template/src/components/login/web.pc.tsx index 972a349a6..36aae4a8e 100644 --- a/template/src/components/login/web.pc.tsx +++ b/template/src/components/login/web.pc.tsx @@ -4,6 +4,7 @@ import { WebComponentProps } from 'oak-frontend-base'; import { Button, Result } from 'antd'; import { EntityDict } from '@project/oak-app-domain'; import Styles from './web.pc.module.less'; +import ByOauth from '@project/components/login/byOauth'; export default function render(props: WebComponentProps +
+ + +
); } diff --git a/template/src/pages/frontend/login/index.json b/template/src/pages/frontend/login/index.json new file mode 100644 index 000000000..7c9dc9981 --- /dev/null +++ b/template/src/pages/frontend/login/index.json @@ -0,0 +1,6 @@ +{ + "enablePullDownRefresh": false, + "usingComponents": { + "login": "@project/components/login/index" + } +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/index.less b/template/src/pages/frontend/login/index.less new file mode 100644 index 000000000..3ce01ef5d --- /dev/null +++ b/template/src/pages/frontend/login/index.less @@ -0,0 +1,7 @@ +.container { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/index.ts b/template/src/pages/frontend/login/index.ts new file mode 100644 index 000000000..2aa06aa58 --- /dev/null +++ b/template/src/pages/frontend/login/index.ts @@ -0,0 +1,36 @@ +export default OakComponent({ + properties: { + redirect: "", + }, + data: { + redirectUrl: "", + }, + methods: { + onLoggedIn() { + const { redirectUrl } = this.state; + console.log('redirecting to', redirectUrl); + if (redirectUrl) { + this.features.navigator.navigateTo({ url: redirectUrl }, undefined, true); + } + }, + loadSearchParams() { + const searchParams = new URLSearchParams(window.location.search); + const encoded = searchParams.get('redirect') || ''; + const redirectUrl = encoded ? decodeURIComponent(atob(encoded)) : ''; + + console.log('Loaded redirect URL from search params:', redirectUrl); + + this.setState({ + redirectUrl: redirectUrl, + }); + }, + }, + lifetimes: { + ready() { + this.loadSearchParams(); + if (this.features.token.getUserId(true)) { + this.onLoggedIn(); + } + }, + } +}); diff --git a/template/src/pages/frontend/login/index.xml b/template/src/pages/frontend/login/index.xml new file mode 100644 index 000000000..c83547525 --- /dev/null +++ b/template/src/pages/frontend/login/index.xml @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/template/src/pages/frontend/login/locales/zh_CN.json b/template/src/pages/frontend/login/locales/zh_CN.json new file mode 100644 index 000000000..e76ebcefe --- /dev/null +++ b/template/src/pages/frontend/login/locales/zh_CN.json @@ -0,0 +1,3 @@ +{ + "pageTitle": "登录" +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/mobile.module.less b/template/src/pages/frontend/login/mobile.module.less new file mode 100644 index 000000000..fabedb4d1 --- /dev/null +++ b/template/src/pages/frontend/login/mobile.module.less @@ -0,0 +1,7 @@ +.container { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 10px; +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/oauth/authorize/index.ts b/template/src/pages/frontend/login/oauth/authorize/index.ts new file mode 100644 index 000000000..d98c939af --- /dev/null +++ b/template/src/pages/frontend/login/oauth/authorize/index.ts @@ -0,0 +1,6 @@ +export default OakComponent({ + // Virtual Component + isList: false, + filters: [], + properties:{} +}); \ No newline at end of file diff --git a/template/src/pages/frontend/login/oauth/authorize/locales/zh_CN.json b/template/src/pages/frontend/login/oauth/authorize/locales/zh_CN.json new file mode 100644 index 000000000..544b7b4dd --- /dev/null +++ b/template/src/pages/frontend/login/oauth/authorize/locales/zh_CN.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/oauth/authorize/styles.module.less b/template/src/pages/frontend/login/oauth/authorize/styles.module.less new file mode 100644 index 000000000..b13913541 --- /dev/null +++ b/template/src/pages/frontend/login/oauth/authorize/styles.module.less @@ -0,0 +1,7 @@ +.id { + font-size: 18px; +} + +.item { + font-size: 18px; +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/oauth/authorize/web.pc.tsx b/template/src/pages/frontend/login/oauth/authorize/web.pc.tsx new file mode 100644 index 000000000..1e3239919 --- /dev/null +++ b/template/src/pages/frontend/login/oauth/authorize/web.pc.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { EntityDict } from '@project/oak-app-domain'; +import { RowWithActions, WebComponentProps } from 'oak-frontend-base'; +import Styles from './styles.module.less'; +import Auth from "oak-general-business/es/components/login/oauth/authorize" + +const Authorize = ( + props: WebComponentProps< + EntityDict, + keyof EntityDict, + false, + { + // virtual + } + > +) => { + return ; +}; + +export default Authorize; \ No newline at end of file diff --git a/template/src/pages/frontend/login/pc.module.less b/template/src/pages/frontend/login/pc.module.less new file mode 100644 index 000000000..5f6625d1d --- /dev/null +++ b/template/src/pages/frontend/login/pc.module.less @@ -0,0 +1,7 @@ +.container { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 22px; +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/web.pc.tsx b/template/src/pages/frontend/login/web.pc.tsx new file mode 100644 index 000000000..64895a69a --- /dev/null +++ b/template/src/pages/frontend/login/web.pc.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import Login from '@project/components/login'; +import Styles from './pc.module.less'; +import { WebComponentProps } from 'oak-frontend-base'; +import { EntityDict } from '@project/oak-app-domain'; + +export default function Render(props: WebComponentProps< + EntityDict, + "user", + false, + {}, + { + onLoggedIn: () => void; + } + >) { + + const { onLoggedIn } = props.methods; + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/template/src/pages/frontend/login/web.tsx b/template/src/pages/frontend/login/web.tsx new file mode 100644 index 000000000..6d0dd9b7e --- /dev/null +++ b/template/src/pages/frontend/login/web.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import Login from '@project/components/login'; +import Styles from './pc.module.less'; + +export default function Render() { + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/template/src/pages/frontend/oauth/index.ts b/template/src/pages/frontend/oauth/index.ts new file mode 100644 index 000000000..c0a0c3cc3 --- /dev/null +++ b/template/src/pages/frontend/oauth/index.ts @@ -0,0 +1,6 @@ +export default OakComponent({ + // Virtual Component + isList: false, + filters: [], + properties:{} +}); diff --git a/template/src/pages/frontend/oauth/locales/zh_CN.json b/template/src/pages/frontend/oauth/locales/zh_CN.json new file mode 100644 index 000000000..544b7b4dd --- /dev/null +++ b/template/src/pages/frontend/oauth/locales/zh_CN.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/template/src/pages/frontend/oauth/styles.module.less b/template/src/pages/frontend/oauth/styles.module.less new file mode 100644 index 000000000..b13913541 --- /dev/null +++ b/template/src/pages/frontend/oauth/styles.module.less @@ -0,0 +1,7 @@ +.id { + font-size: 18px; +} + +.item { + font-size: 18px; +} \ No newline at end of file diff --git a/template/src/pages/frontend/oauth/web.pc.tsx b/template/src/pages/frontend/oauth/web.pc.tsx new file mode 100644 index 000000000..0f4097e51 --- /dev/null +++ b/template/src/pages/frontend/oauth/web.pc.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { EntityDict } from '@project/oak-app-domain'; +import { RowWithActions, WebComponentProps } from 'oak-frontend-base'; +import Styles from './styles.module.less'; +import OAuth from "oak-general-business/es/components/oauth" + +const Oauth = ( + props: WebComponentProps< + EntityDict, + keyof EntityDict, + false, + { + // virtual + } + > +) => { + const { oakFullpath } = props.data; + + return +}; + +export default Oauth; \ No newline at end of file