release(main): 同步登录邮箱后缀兼容修复
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
This commit is contained in:
@@ -96,12 +96,17 @@ describe("Login protocol agreement", () => {
|
|||||||
expect(source).toContain("max-width: calc(100vw - 40px);");
|
expect(source).toContain("max-width: calc(100vw - 40px);");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses only server-configured email domains on web and desktop", () => {
|
it("falls back to full email input when no server-configured domains exist", () => {
|
||||||
const source = readLoginView();
|
const source = readLoginView();
|
||||||
|
|
||||||
expect(source).toContain('fetchEmailDomains()');
|
expect(source).toContain('fetchEmailDomains()');
|
||||||
expect(source).toContain(':disabled="availableEmailDomains.length === 0"');
|
|
||||||
expect(source).toContain("const availableEmailDomains = computed(() => configuredEmailDomains.value)");
|
expect(source).toContain("const availableEmailDomains = computed(() => configuredEmailDomains.value)");
|
||||||
|
expect(source).toContain("availableEmailDomains.value.length > 0 && availableEmailDomains.value.includes(form.emailDomain)");
|
||||||
|
expect(source).toContain("const manualEmailInput = computed(() => !hasConfiguredEmailDomains.value)");
|
||||||
|
expect(source).toContain('v-if="hasConfiguredEmailDomains"');
|
||||||
|
expect(source).toContain("未检测到邮箱后缀配置,请输入完整邮箱地址。");
|
||||||
|
expect(source).toContain("form.email = local;");
|
||||||
|
expect(source).toContain("if (domains.length === 0)");
|
||||||
expect(source).toContain("configuredEmailDomains.value.includes(domain)");
|
expect(source).toContain("configuredEmailDomains.value.includes(domain)");
|
||||||
expect(source).not.toContain("preservedEmailDomain");
|
expect(source).not.toContain("preservedEmailDomain");
|
||||||
expect(source).not.toContain("showDomainSelect");
|
expect(source).not.toContain("showDomainSelect");
|
||||||
|
|||||||
@@ -145,19 +145,19 @@
|
|||||||
<!-- 登录表单 -->
|
<!-- 登录表单 -->
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" @keyup.enter="onSubmit" label-position="top" class="login-form">
|
<el-form ref="formRef" :model="form" :rules="rules" @keyup.enter="onSubmit" label-position="top" class="login-form">
|
||||||
<el-form-item label="账号" prop="email">
|
<el-form-item label="账号" prop="email">
|
||||||
<div class="email-unified-box">
|
<div class="email-unified-box" :class="{ 'email-unified-box--manual': manualEmailInput }">
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
v-model="form.emailLocal"
|
v-model="form.emailLocal"
|
||||||
type="text"
|
:type="manualEmailInput ? 'email' : 'text'"
|
||||||
placeholder="请输入账号"
|
:placeholder="emailInputPlaceholder"
|
||||||
name="username"
|
name="username"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
class="email-local-field"
|
class="email-local-field"
|
||||||
@paste="handleAccountPaste"
|
@paste="handleAccountPaste"
|
||||||
/>
|
/>
|
||||||
<span class="email-at-sign">@</span>
|
<span v-if="hasConfiguredEmailDomains" class="email-at-sign">@</span>
|
||||||
<div class="email-domain-wrapper">
|
<div v-if="hasConfiguredEmailDomains" class="email-domain-wrapper">
|
||||||
<select
|
<select
|
||||||
v-model="form.emailDomain"
|
v-model="form.emailDomain"
|
||||||
class="email-domain-field"
|
class="email-domain-field"
|
||||||
@@ -169,6 +169,7 @@
|
|||||||
<svg class="email-domain-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m6 9 6 6 6-6"/></svg>
|
<svg class="email-domain-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m6 9 6 6 6-6"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="manualEmailInput" class="email-manual-hint">未检测到邮箱后缀配置,请输入完整邮箱地址。</p>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="密码" prop="password">
|
<el-form-item label="密码" prop="password">
|
||||||
@@ -294,9 +295,19 @@ const refreshDesktopServerUrl = () => {
|
|||||||
|
|
||||||
const normalizeDomain = (value: string) => value.trim().toLowerCase().replace(/^@/, "");
|
const normalizeDomain = (value: string) => value.trim().toLowerCase().replace(/^@/, "");
|
||||||
const availableEmailDomains = computed(() => configuredEmailDomains.value);
|
const availableEmailDomains = computed(() => configuredEmailDomains.value);
|
||||||
|
const hasConfiguredEmailDomains = computed(() =>
|
||||||
|
availableEmailDomains.value.length > 0 && availableEmailDomains.value.includes(form.emailDomain)
|
||||||
|
);
|
||||||
|
const manualEmailInput = computed(() => !hasConfiguredEmailDomains.value);
|
||||||
|
const emailInputPlaceholder = computed(() => manualEmailInput.value ? "请输入完整邮箱" : "请输入账号");
|
||||||
|
|
||||||
const syncEmailFromParts = () => {
|
const syncEmailFromParts = () => {
|
||||||
const local = form.emailLocal.trim().toLowerCase();
|
const local = form.emailLocal.trim().toLowerCase();
|
||||||
|
if (manualEmailInput.value) {
|
||||||
|
form.emailDomain = "";
|
||||||
|
form.email = local;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const domain = normalizeDomain(form.emailDomain);
|
const domain = normalizeDomain(form.emailDomain);
|
||||||
form.emailDomain = domain;
|
form.emailDomain = domain;
|
||||||
form.email = local && domain ? `${local}@${domain}` : "";
|
form.email = local && domain ? `${local}@${domain}` : "";
|
||||||
@@ -304,6 +315,12 @@ const syncEmailFromParts = () => {
|
|||||||
|
|
||||||
const applyEmailValue = (email: string) => {
|
const applyEmailValue = (email: string) => {
|
||||||
const normalized = email.trim().toLowerCase();
|
const normalized = email.trim().toLowerCase();
|
||||||
|
if (availableEmailDomains.value.length === 0) {
|
||||||
|
form.emailLocal = normalized;
|
||||||
|
form.emailDomain = "";
|
||||||
|
syncEmailFromParts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const separator = normalized.lastIndexOf("@");
|
const separator = normalized.lastIndexOf("@");
|
||||||
if (separator > 0) {
|
if (separator > 0) {
|
||||||
form.emailLocal = normalized.slice(0, separator);
|
form.emailLocal = normalized.slice(0, separator);
|
||||||
@@ -321,16 +338,32 @@ const applyEmailValue = (email: string) => {
|
|||||||
const loadEmailDomains = async () => {
|
const loadEmailDomains = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await fetchEmailDomains();
|
const { data } = await fetchEmailDomains();
|
||||||
configuredEmailDomains.value = Array.from(new Set(
|
const domains = Array.from(new Set(
|
||||||
data.items.map(normalizeDomain).filter(Boolean)
|
(Array.isArray(data.items) ? data.items : [])
|
||||||
|
.map(item => typeof item === "string" ? normalizeDomain(item) : "")
|
||||||
|
.filter(Boolean)
|
||||||
));
|
));
|
||||||
if (!configuredEmailDomains.value.includes(form.emailDomain)) {
|
configuredEmailDomains.value = domains;
|
||||||
form.emailDomain = configuredEmailDomains.value[0] || "";
|
|
||||||
|
if (domains.length === 0) {
|
||||||
|
form.emailDomain = "";
|
||||||
|
syncEmailFromParts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.emailLocal.includes("@")) {
|
||||||
|
applyEmailValue(form.emailLocal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!domains.includes(form.emailDomain)) {
|
||||||
|
form.emailDomain = domains[0];
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
configuredEmailDomains.value = [];
|
configuredEmailDomains.value = [];
|
||||||
form.emailDomain = "";
|
form.emailDomain = "";
|
||||||
}
|
}
|
||||||
|
syncEmailFromParts();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAccountPaste = (event: ClipboardEvent) => {
|
const handleAccountPaste = (event: ClipboardEvent) => {
|
||||||
@@ -367,6 +400,7 @@ const confirmProtocol = () => { form.agreeProtocol = true; protocolDialogVisible
|
|||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
if (!formRef.value) return;
|
if (!formRef.value) return;
|
||||||
|
syncEmailFromParts();
|
||||||
const valid = await formRef.value.validate();
|
const valid = await formRef.value.validate();
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
if (!form.agreeProtocol) { ElMessage.warning("请先阅读并同意用户协议"); return; }
|
if (!form.agreeProtocol) { ElMessage.warning("请先阅读并同意用户协议"); return; }
|
||||||
@@ -948,6 +982,10 @@ const onSubmit = async () => {
|
|||||||
border-bottom-color: #2563eb;
|
border-bottom-color: #2563eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.email-unified-box--manual .email-local-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.email-local-field,
|
.email-local-field,
|
||||||
.email-domain-field {
|
.email-domain-field {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -969,6 +1007,13 @@ const onSubmit = async () => {
|
|||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.email-manual-hint {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.email-at-sign {
|
.email-at-sign {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user