|
@@ -199,6 +199,23 @@ type LoginMethod = 'mobile' | 'token';
|
|
|
<p class="hint"><i class="fa-solid fa-circle-info" aria-hidden="true"></i>SessionToken 可在 Fmode 平台账号页查看</p>
|
|
<p class="hint"><i class="fa-solid fa-circle-info" aria-hidden="true"></i>SessionToken 可在 Fmode 平台账号页查看</p>
|
|
|
}
|
|
}
|
|
|
</ng-template>
|
|
</ng-template>
|
|
|
|
|
+
|
|
|
|
|
+ <!--
|
|
|
|
|
+ 🔴 端切换提示(与 mobile-shell 同口径)。
|
|
|
|
|
+ 场景:端判定为 mobile,但当前窗口已达桌面宽度 → 提示可切到桌面端。
|
|
|
|
|
+ 此前 /login 不在任何 shell 内,**缺这个提示**,用户在电脑上遇到
|
|
|
|
|
+ mobile 版式时无从切换。
|
|
|
|
|
+ -->
|
|
|
|
|
+ @if (isMobile() && wideViewport()) {
|
|
|
|
|
+ <div class="wide-login" role="alertdialog" aria-label="窗口适合桌面端">
|
|
|
|
|
+ <div class="wide-card">
|
|
|
|
|
+ <span class="wide-icon" aria-hidden="true"><i class="fa-solid fa-display"></i></span>
|
|
|
|
|
+ <h2>当前窗口适合桌面端</h2>
|
|
|
|
|
+ <p>已检测到窗口宽度达到 768px,可以切换到桌面布局继续使用。</p>
|
|
|
|
|
+ <button type="button" class="wide-btn" (click)="goDesktop()">切换到桌面端</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ }
|
|
|
`,
|
|
`,
|
|
|
styles: [
|
|
styles: [
|
|
|
`
|
|
`
|
|
@@ -592,6 +609,56 @@ type LoginMethod = 'mobile' | 'token';
|
|
|
display: none;
|
|
display: none;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /* 端切换提示层(与 mobile-shell 的 .wide-mobile 同口径) */
|
|
|
|
|
+ .wide-login {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ z-index: var(--z-mask);
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ padding: var(--sp-6);
|
|
|
|
|
+ background: var(--canvas);
|
|
|
|
|
+ }
|
|
|
|
|
+ .wide-card {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ justify-items: center;
|
|
|
|
|
+ gap: var(--sp-2);
|
|
|
|
|
+ max-width: 360px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .wide-icon {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ width: 56px;
|
|
|
|
|
+ height: 56px;
|
|
|
|
|
+ border-radius: var(--r-lg);
|
|
|
|
|
+ background: var(--primary-subtle);
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ font-size: var(--fs-20);
|
|
|
|
|
+ }
|
|
|
|
|
+ .wide-card h2 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ color: var(--ink);
|
|
|
|
|
+ font-size: var(--fs-20);
|
|
|
|
|
+ }
|
|
|
|
|
+ .wide-card p {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ color: var(--muted);
|
|
|
|
|
+ font-size: var(--fs-13);
|
|
|
|
|
+ line-height: 1.7;
|
|
|
|
|
+ }
|
|
|
|
|
+ .wide-btn {
|
|
|
|
|
+ min-height: var(--touch-min);
|
|
|
|
|
+ margin-top: var(--sp-2);
|
|
|
|
|
+ padding: 0 var(--sp-4);
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: var(--r-sm);
|
|
|
|
|
+ background: var(--primary);
|
|
|
|
|
+ color: var(--primary-content);
|
|
|
|
|
+ font-size: var(--fs-13);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
`,
|
|
`,
|
|
|
],
|
|
],
|
|
|
})
|
|
})
|
|
@@ -611,7 +678,19 @@ export class LoginComponent {
|
|
|
readonly busy = signal(false);
|
|
readonly busy = signal(false);
|
|
|
readonly sendingCode = signal(false);
|
|
readonly sendingCode = signal(false);
|
|
|
readonly countdown = signal(0);
|
|
readonly countdown = signal(0);
|
|
|
- readonly isMobile = signal(this.device.mode() === 'mobile');
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 是否移动端版式。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 🔴 用 `computed` 而非 `signal(...)` —— `signal()` 会在**构造时求值一次**并固定,
|
|
|
|
|
+ * 无法反映 `device.mode()` 的后续变化(如用户在页面上手动切端)。
|
|
|
|
|
+ * `computed` 跟随 `device.mode()` 的 signal 自动更新。
|
|
|
|
|
+ */
|
|
|
|
|
+ readonly isMobile = computed(() => this.device.mode() === 'mobile');
|
|
|
|
|
+
|
|
|
|
|
+ /** 窗口是否达到桌面宽度(用于「切到桌面端」提示,与 mobile-shell 同口径) */
|
|
|
|
|
+ readonly wideViewport = signal(
|
|
|
|
|
+ typeof window !== 'undefined' && window.innerWidth >= 768,
|
|
|
|
|
+ );
|
|
|
readonly mobileValid = computed(() => /^1\d{10}$/.test(this.mobile()));
|
|
readonly mobileValid = computed(() => /^1\d{10}$/.test(this.mobile()));
|
|
|
readonly codeValid = computed(() => /^\d{6}$/.test(this.code()));
|
|
readonly codeValid = computed(() => /^\d{6}$/.test(this.code()));
|
|
|
readonly tokenValid = computed(() => this.token().trim().length > 0);
|
|
readonly tokenValid = computed(() => this.token().trim().length > 0);
|
|
@@ -770,6 +849,17 @@ export class LoginComponent {
|
|
|
await this.router.navigateByUrl(this.device.normalize(target), { replaceUrl: true });
|
|
await this.router.navigateByUrl(this.device.normalize(target), { replaceUrl: true });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 切换到桌面端(登录页的端切换提示用)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 与 `mobile-shell` 的 `goDesktop()` 同逻辑:写偏好 → 跳桌面落地页。
|
|
|
|
|
+ * 登录页不在 shell 内,故需自带这个出口。
|
|
|
|
|
+ */
|
|
|
|
|
+ goDesktop(): void {
|
|
|
|
|
+ const { path } = this.device.switchMode();
|
|
|
|
|
+ void this.router.navigateByUrl(path, { replaceUrl: true });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private messageOf(e: unknown): string {
|
|
private messageOf(e: unknown): string {
|
|
|
return e instanceof Error && e.message ? e.message : '登录失败,请稍后重试';
|
|
return e instanceof Error && e.message ? e.message : '登录失败,请稍后重试';
|
|
|
}
|
|
}
|