All checks were successful
Build and Deploy Front / deploy (push) Successful in 7m11s
登录仅保留账号与微信;注册先选渠道,代理码仅 URL 携带时自动绑定。 Co-authored-by: Cursor <cursoragent@cursor.com>
698 lines
24 KiB
Vue
698 lines
24 KiB
Vue
<template>
|
||
<div class="nova-login">
|
||
<a
|
||
class="nova-login__header"
|
||
href="/"
|
||
:title="brandHint"
|
||
@click.prevent="goOfficialSite"
|
||
>
|
||
<LoginLockLogo />
|
||
<p class="nova-login__brand-hint">{{ brandHint }}</p>
|
||
</a>
|
||
|
||
<form class="nova-login__form" autocomplete="off" @submit.prevent="handleSubmit">
|
||
<div class="nova-login__input-group">
|
||
<div class="nova-login__field-label">{{ accountFieldLabel }}</div>
|
||
<a-input
|
||
v-model="loginForm.account"
|
||
size="large"
|
||
allow-clear
|
||
class="nova-login__arco-input"
|
||
:placeholder="accountPlaceholder"
|
||
autocomplete="username"
|
||
@focus="onAccountFocus"
|
||
@blur="onAccountBlur"
|
||
@press-enter="focusPasswordInput"
|
||
/>
|
||
<div v-if="accountError" class="nova-login__field-error">{{ accountError }}</div>
|
||
</div>
|
||
|
||
<div class="nova-login__input-group">
|
||
<div class="nova-login__field-label">{{ t('login.field.password') }}</div>
|
||
<a-input-password
|
||
ref="passwordInputRef"
|
||
v-model="loginForm.password"
|
||
size="large"
|
||
allow-clear
|
||
class="nova-login__arco-input"
|
||
:placeholder="t('login.form.password.placeholder')"
|
||
autocomplete="current-password"
|
||
@focus="onPasswordFocus"
|
||
@blur="onPasswordBlur"
|
||
@input="syncPasswordCharacterState"
|
||
@press-enter="handleSubmit"
|
||
@visibility-change="onPasswordVisibilityChange"
|
||
/>
|
||
<div v-if="passwordError" class="nova-login__field-error">{{ passwordError }}</div>
|
||
</div>
|
||
|
||
<div v-if="errorBox" class="nova-login__error-box">{{ errorBox }}</div>
|
||
|
||
<button type="submit" class="nova-login__submit" :disabled="loading">
|
||
{{ loading ? t('login.submitting') : t('login.form.login') }}
|
||
</button>
|
||
|
||
<div v-if="isCustomerLogin" class="agreement-area">
|
||
<a-checkbox v-model="agreementChecked">
|
||
<span class="agreement-line">
|
||
{{ t('login.agreement.readPrefix') }}
|
||
<a-link @click.stop="openAgreement('privacy')">{{ t('login.agreement.privacyLink') }}</a-link>
|
||
{{ t('login.agreement.and') }}
|
||
<a-link @click.stop="openAgreement('terms')">{{ t('login.agreement.termsLink') }}</a-link>
|
||
</span>
|
||
</a-checkbox>
|
||
</div>
|
||
</form>
|
||
|
||
<div v-if="props.loginType === OmsLoginType.Customer" class="login-alt-entry">
|
||
<div class="login-alt-entry__divider">
|
||
<span>{{ t('login.altEntry.or') }}</span>
|
||
</div>
|
||
<a-tooltip :content="t('login.switchToWechat')">
|
||
<button
|
||
type="button"
|
||
class="wechat-entry-btn"
|
||
:aria-label="t('login.switchToWechat')"
|
||
@mouseenter="emit('prefetchWechat')"
|
||
@focus="emit('prefetchWechat')"
|
||
@click="emit('switchWechat')"
|
||
>
|
||
<icon-wechat :size="28" />
|
||
</button>
|
||
</a-tooltip>
|
||
<p class="login-alt-entry__hint">
|
||
{{ t('login.noAccount') }}
|
||
<a-link @click="goRegister">{{ t('login.goRegister') }}</a-link>
|
||
</p>
|
||
</div>
|
||
|
||
<a-modal
|
||
v-model:visible="agreementVisible"
|
||
:title="agreementTitle"
|
||
:footer="false"
|
||
width="860px"
|
||
unmount-on-close
|
||
>
|
||
<div class="agreement-content">{{ agreementContent }}</div>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, nextTick, inject, type ShallowRef } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { useI18n } from 'vue-i18n';
|
||
import { Message } from '@arco-design/web-vue';
|
||
import { IconWechat } from '@arco-design/web-vue/es/icon';
|
||
import { OmsLoginType } from '@/api/rbac';
|
||
import { useRbacUserStore } from '@/store/modules/rbac-user';
|
||
import { setToken } from '@/utils/auth';
|
||
import { getDefaultHomePathByType, resolveCustomerEntryPath } from '@/utils/login-portal';
|
||
import { enterSystemAfterLogin, applyRbacUserToUserStore } from '@/utils/enter-system';
|
||
import { ensureHeavyPlugins } from '@/plugins/heavy-plugins';
|
||
import { LOGIN_CHARACTERS_KEY, type LoginCharactersApi } from '../../composables/useLoginCharacters';
|
||
import { LOGIN_EXIT_KEY, type LoginExitApi } from '../../composables/useLoginExit';
|
||
import LoginLockLogo from '../LoginLockLogo.vue';
|
||
|
||
const { t } = useI18n();
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
loginType?: OmsLoginType;
|
||
}>(),
|
||
{
|
||
loginType: OmsLoginType.User,
|
||
},
|
||
);
|
||
|
||
const emit = defineEmits<{
|
||
switchWechat: [];
|
||
prefetchWechat: [];
|
||
}>();
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const rbacUserStore = useRbacUserStore();
|
||
|
||
function goRegister() {
|
||
const query: Record<string, string> = {};
|
||
for (const key of ['agent', 'agentCode', 'ref', 'invite', 'advisor', 'redirect'] as const) {
|
||
const raw = route.query[key];
|
||
const value = typeof raw === 'string' ? raw.trim() : '';
|
||
if (value) query[key] = value;
|
||
}
|
||
void router.push({ path: '/register', query });
|
||
}
|
||
|
||
const passwordInputRef = ref<{ focus?: () => void } | null>(null);
|
||
const loading = ref(false);
|
||
const showPassword = ref(false);
|
||
const accountError = ref('');
|
||
const passwordError = ref('');
|
||
const errorBox = ref('');
|
||
const loginCharactersRef = inject<ShallowRef<LoginCharactersApi | null>>(LOGIN_CHARACTERS_KEY);
|
||
const loginExit = inject<LoginExitApi | null>(LOGIN_EXIT_KEY, null);
|
||
const loginCharacters = computed(() => loginCharactersRef?.value ?? null);
|
||
|
||
const isCustomerLogin = computed(() => props.loginType === OmsLoginType.Customer);
|
||
const isAgentLogin = computed(() => props.loginType === OmsLoginType.Agent);
|
||
const brandHint = computed(() => {
|
||
if (isCustomerLogin.value) return t('login.form.brandHint.customer');
|
||
if (isAgentLogin.value) return t('login.form.brandHint.agent');
|
||
return t('login.form.brandHint.admin');
|
||
});
|
||
|
||
/** Logo / 欢迎文案 → 官网首页 */
|
||
function goOfficialSite() {
|
||
window.location.href = '/';
|
||
}
|
||
const accountFieldLabel = computed(() =>
|
||
isAgentLogin.value ? t('login.field.account.agent') : t('login.field.account'),
|
||
);
|
||
const accountPlaceholder = computed(() => {
|
||
if (isCustomerLogin.value) return t('login.form.account.placeholder.customer');
|
||
if (isAgentLogin.value) return t('login.form.account.placeholder.agent');
|
||
return t('login.form.account.placeholder.admin');
|
||
});
|
||
const accountRequiredMsg = computed(() => {
|
||
if (isCustomerLogin.value) return t('login.form.account.errMsg.customer');
|
||
if (isAgentLogin.value) return t('login.form.account.errMsg.agent');
|
||
return t('login.form.account.errMsg.admin');
|
||
});
|
||
|
||
const agreementChecked = ref(true);
|
||
const agreementVisible = ref(false);
|
||
const agreementTitle = ref('');
|
||
const agreementContent = ref('');
|
||
|
||
const loginForm = reactive({
|
||
account: '',
|
||
password: '',
|
||
});
|
||
|
||
const privacyAgreementText = `一、隐私保护告知
|
||
(一)前言
|
||
欢迎使用本物流公司(以下简称“我方”)平台及物流服务。为保护您的个人隐私和数据安全,依据相关法律法规,结合物流业务实际,现将我方对您信息的处理相关事宜告知如下。您完成注册、登录并使用我方服务,即视为您已阅读、理解并同意本告知全部内容;若您不同意,请立即停止注册、登录及使用我方服务。
|
||
(二)信息采集范围及方式
|
||
1. 采集范围
|
||
遵循“最小必要性”原则,仅采集实现物流服务必需的信息:
|
||
• 基本身份信息:个人客户需提供姓名、手机号、身份证号(用于实名认证);企业客户需提供企业名称、统一社会信用代码、联系人信息。
|
||
• 注册登录信息:您设置的用户名、加密密码、验证码及登录设备相关信息。
|
||
• 物流相关信息:寄收件地址、货物描述、联系方式(将进行去标识化处理,隐藏敏感字段)。
|
||
• 服务交互信息:您的订单、查询、投诉、评价等记录及偏好设置。
|
||
• 敏感信息:身份证号、银行卡号(仅在必要时采集,将单独加密,需您单独同意)。
|
||
2. 采集方式
|
||
• 客户主动提供:您在注册、下单、实名认证等过程中自愿提交的信息。
|
||
• 系统自动生成:通过我方物流跟踪、订单管理等系统自动生成的日志、轨迹等信息。
|
||
• 合作方共享:经您同意,与合法合作方共享必要信息(如支付、订单信息)。
|
||
我方严禁违规、过度采集无关信息。
|
||
(三)信息存储与保密
|
||
1. 存储管理
|
||
• 您的信息将存储在合规安全服务器或云平台,严禁存储在个人设备及未授权第三方平台。
|
||
• 存储期限:基本身份信息留存至您注销账户后1年;交易物流信息留存至交易完成后3年;服务交互信息留存至服务结束后2年;敏感信息实现采集目的后立即匿名化或销毁。
|
||
• 我方将定期备份信息,对超过存储期限的信息,将通过不可逆方式销毁并做好记录。
|
||
2. 保密措施
|
||
• 实行最小权限授权,定期核查、收回员工信息访问权限。
|
||
• 所有接触您信息的员工及合作方,均需签订保密协议,我方将定期开展保密培训。
|
||
• 采用加密算法、防火墙等技术,保障信息存储和传输安全,定期进行漏洞扫描及修复。
|
||
(四)信息使用规则
|
||
我方对您信息的使用,仅用于实现采集时告知的目的,具体包括:提供物流核心服务、账号管理与安全保障、服务优化、合规配合,以及经您同意的营销推送(您可随时取消)。我方严禁违规使用您的信息。
|
||
(五)信息共享与披露
|
||
1. 信息共享
|
||
仅向实现服务必需的合作方(如支付机构、物流合作方、技术服务商)共享您的信息,我方将与合作方签订共享协议,明确保密义务,严禁向无关第三方共享、出售您的信息。
|
||
2. 信息披露
|
||
未经您同意,我方不会向任何第三方披露您的信息,除非符合法律法规、司法行政机关要求,或为保护您及他人人身财产安全、您自行授权披露。
|
||
(六)您的相关权利
|
||
您享有对自身信息的知情权、访问权、更正权、删除权、撤回同意权及账号注销权,可通过平台相关入口或联系客服行使上述权利,我方将及时配合处理。
|
||
(七)信息安全风险提示
|
||
我方已采取相应安全措施,尽力保障您的信息安全,但不排除网络传输等客观风险;请您妥善保管自身账号信息,通过第三方平台登录时,需遵守该第三方的隐私相关规定。
|
||
(八)告知内容的更新与修改
|
||
我方将根据法律法规及业务变化,不定期更新本告知内容,更新后将通过平台公告通知您,您继续使用我方服务,即视为同意更新后的告知内容。`;
|
||
|
||
const termsAgreementText = `二、使用须知告知
|
||
(一)告知效力
|
||
本使用须知仅为我方对您使用平台及相关物流服务的事项告知,您完成注册、登录并使用我方服务,即视为您已阅读、理解并遵守本须知全部内容。
|
||
(二)适用范围
|
||
本须知适用于您使用我方平台进行注册、登录、查询物流、下单寄件、投诉反馈等所有相关服务的行为,包括我方提供的各类物流增值服务。
|
||
(三)您的权利与注意事项
|
||
1. 您的权利
|
||
您有权使用我方平台提供的物流服务及相关增值服务,自主查询物流轨迹、订单信息,对我方服务进行评价、投诉,维护自身合法权益。
|
||
2. 注意事项
|
||
• 注册时请提供真实、准确、完整的信息,信息发生变更请及时更新,否则因此产生的服务延误、账号异常等后果,由您自行承担。
|
||
• 请妥善保管您的账号、密码、验证码等信息,对账号的所有操作承担责任,若发现账号被盗、冒用,请立即联系我方客服。
|
||
• 使用服务时请遵守国家法律法规及我方平台规则,不得利用账号从事违法违规活动(如违法寄递违禁品、欺诈等),否则我方有权暂停或终止为您提供服务,由此产生的损失由您自行承担。
|
||
• 下单寄件时,请如实提供寄收件信息及货物描述,配合我方完成实名登记、货物查验,若拒绝配合,我方有权拒绝提供服务。
|
||
• 请认可我方平台公示的物流服务收费标准,按时支付相关费用,逾期未支付的,我方有权暂停服务。
|
||
(四)我方的服务说明
|
||
1. 服务权利
|
||
我方有权根据法律法规、业务需要,制定、修改平台规则、收费标准等相关内容,并通过平台公告通知您。
|
||
我方有权对您的注册信息、账号操作、货物信息进行核查,若发现您存在违规行为,有权暂停或终止服务。
|
||
因不可抗力(如自然灾害、交通管制等)、第三方原因导致无法正常提供服务的,我方有权暂停服务,并及时通知您,不承担相关责任。
|
||
2. 服务义务
|
||
我方将为您提供安全、便捷、高效的物流服务,及时更新物流轨迹信息,保障货物安全,严格保护您的个人信息。
|
||
我方将在平台公示服务流程、收费标准、违禁品清单等相关信息,提供完善的客服服务,及时响应您的咨询、投诉。
|
||
(五)账号管理说明
|
||
一个客户仅能注册一个平台账号,注册后需完成实名认证方可正常使用下单等服务;账号仅限您本人使用,不得出租、出借、转让;您可通过平台“忘记密码”功能找回密码,也可按提示注销账号(注销后不可恢复)。
|
||
(六)服务费用说明
|
||
物流服务费用按平台公示标准收取(根据货物重量、体积、运输距离等因素计算),增值服务需另行付费,具体费用以您下单时平台显示金额为准;您可通过平台支持的支付方式付款,逾期支付需按约定支付违约金。
|
||
(七)责任说明
|
||
1. 您的责任
|
||
若您违反本须知约定,提供虚假信息、违规使用账号、逾期付费等,由此产生的损失及法律责任,由您自行承担,我方有权暂停或终止服务。
|
||
2. 我方的责任
|
||
若因我方自身原因,未按约定提供服务、泄露您的个人信息,给您造成损失的,我方将承担相应责任。
|
||
(八)免责说明
|
||
因不可抗力、您自身原因、货物本身性质、第三方原因等导致的损失,我方不承担相关责任。
|
||
(九)须知更新与服务终止
|
||
我方可根据实际情况更新本须知,更新后通过平台公告通知您,您继续使用服务即视为同意更新内容。
|
||
您可随时注销账号终止使用服务;若您存在违规行为,我方有权终止服务,服务终止后,我方将按隐私保护告知约定处理您的相关信息。
|
||
(十)争议处理
|
||
您与我方就服务使用产生的争议,优先友好协商解决;协商不成的,可向我方所在地有管辖权的人民法院提起诉讼。
|
||
(十一)其他说明
|
||
本须知条款无效不影响其他条款效力;您通过我方平台使用第三方服务,需遵守第三方相关规定;未尽事宜,按国家相关法律法规执行。
|
||
更新日期:2026年4月27日`;
|
||
|
||
function openAgreement(type: 'privacy' | 'terms') {
|
||
if (type === 'privacy') {
|
||
agreementTitle.value = '隐私保护告知';
|
||
agreementContent.value = privacyAgreementText;
|
||
} else {
|
||
agreementTitle.value = '使用须知告知';
|
||
agreementContent.value = termsAgreementText;
|
||
}
|
||
agreementVisible.value = true;
|
||
}
|
||
|
||
function syncPasswordCharacterState() {
|
||
loginCharacters.value?.onPasswordInput(loginForm.password.length, showPassword.value);
|
||
}
|
||
|
||
function onPasswordVisibilityChange(visible: boolean) {
|
||
showPassword.value = visible;
|
||
syncPasswordCharacterState();
|
||
}
|
||
|
||
function onAccountFocus() {
|
||
accountError.value = '';
|
||
loginCharacters.value?.onAccountFocus();
|
||
void ensureHeavyPlugins();
|
||
}
|
||
|
||
function onAccountBlur() {
|
||
loginCharacters.value?.onAccountBlur();
|
||
}
|
||
|
||
function onPasswordFocus() {
|
||
passwordError.value = '';
|
||
loginCharacters.value?.onPasswordFocus();
|
||
void ensureHeavyPlugins();
|
||
}
|
||
|
||
function onPasswordBlur() {
|
||
loginCharacters.value?.onPasswordBlur();
|
||
}
|
||
|
||
function focusPasswordInput() {
|
||
nextTick(() => {
|
||
passwordInputRef.value?.focus();
|
||
});
|
||
}
|
||
|
||
function validateForm(): boolean {
|
||
accountError.value = '';
|
||
passwordError.value = '';
|
||
errorBox.value = '';
|
||
let valid = true;
|
||
|
||
if (!loginForm.account.trim()) {
|
||
accountError.value = accountRequiredMsg.value;
|
||
valid = false;
|
||
}
|
||
if (!loginForm.password) {
|
||
passwordError.value = t('login.form.password.errMsg');
|
||
valid = false;
|
||
}
|
||
if (!valid) {
|
||
loginCharacters.value?.onLoginFailed();
|
||
}
|
||
return valid;
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
if (loading.value) return;
|
||
|
||
if (isCustomerLogin.value && !agreementChecked.value) {
|
||
Message.warning('请先阅读并勾选同意隐私政策与使用须知');
|
||
return;
|
||
}
|
||
if (!validateForm()) return;
|
||
|
||
try {
|
||
loading.value = true;
|
||
errorBox.value = '';
|
||
|
||
const success = await rbacUserStore.login(
|
||
loginForm.account,
|
||
loginForm.password,
|
||
props.loginType,
|
||
);
|
||
|
||
if (success && rbacUserStore.userInfo) {
|
||
const userInfoData = rbacUserStore.userInfo;
|
||
const fallbackAccount = loginForm.account?.trim() || '';
|
||
|
||
if (rbacUserStore.token) {
|
||
setToken(rbacUserStore.token);
|
||
}
|
||
|
||
applyRbacUserToUserStore(userInfoData, fallbackAccount);
|
||
|
||
const { redirect, ...othersQuery } = router.currentRoute.value.query;
|
||
const redirectStr = redirect as string | undefined;
|
||
const defaultHomePath = getDefaultHomePathByType(props.loginType);
|
||
|
||
const redirectParams: Record<string, unknown> = {
|
||
query: { ...othersQuery },
|
||
};
|
||
|
||
if (redirectStr) {
|
||
if (redirectStr.startsWith('/')) {
|
||
redirectParams.path =
|
||
props.loginType === OmsLoginType.Customer
|
||
? resolveCustomerEntryPath(redirectStr)
|
||
: redirectStr;
|
||
} else {
|
||
redirectParams.name = redirectStr;
|
||
}
|
||
} else {
|
||
redirectParams.path = defaultHomePath;
|
||
}
|
||
|
||
Message.success(t('login.form.login.success'));
|
||
await nextTick();
|
||
await enterSystemAfterLogin({
|
||
target: redirectParams,
|
||
fallbackPath: defaultHomePath,
|
||
playExit: () => loginExit?.playExitAnimation() ?? Promise.resolve(),
|
||
});
|
||
} else {
|
||
errorBox.value = t('login.form.login.failed');
|
||
loginCharacters.value?.onLoginFailed();
|
||
}
|
||
} catch {
|
||
errorBox.value = t('login.form.login.failed');
|
||
loginCharacters.value?.onLoginFailed();
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.nova-login__header {
|
||
display: block;
|
||
text-align: center;
|
||
margin-bottom: 28px;
|
||
color: inherit;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.nova-login__brand-hint {
|
||
margin: 8px 0 0;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
color: rgba(15, 39, 68, 0.55);
|
||
}
|
||
|
||
.nova-login {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
.nova-login__form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
.nova-login__input-group {
|
||
margin-bottom: 20px;
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
.nova-login__field-label {
|
||
margin-bottom: 6px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #334155;
|
||
letter-spacing: 0.3px;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* 与 H5 `.h5-login-form` 输入井一致 */
|
||
.nova-login__form :deep(.arco-input-wrapper),
|
||
.nova-login__form :deep(.arco-input-password) {
|
||
border-radius: 12px;
|
||
background: #f0f3f4;
|
||
border: none;
|
||
min-height: 48px;
|
||
backdrop-filter: none;
|
||
-webkit-backdrop-filter: none;
|
||
box-shadow: inset 0 2px 8px rgba(15, 23, 42, 0.08);
|
||
transition: box-shadow 0.2s ease, background 0.2s ease;
|
||
|
||
&:hover {
|
||
background: #f0f3f4;
|
||
box-shadow: inset 0 3px 10px rgba(15, 23, 42, 0.1);
|
||
}
|
||
|
||
&.arco-input-wrapper-focus,
|
||
&.arco-input-focus {
|
||
background: #f0f3f4;
|
||
border: none;
|
||
box-shadow:
|
||
inset 0 3px 10px rgba(15, 23, 42, 0.1),
|
||
0 0 0 3px color-mix(in srgb, var(--app-primary-color) 12%, transparent);
|
||
}
|
||
}
|
||
|
||
/* 覆盖 Chrome/Edge 自动填充的天蓝色底,保持与输入井 #f0f3f4 一致 */
|
||
.nova-login__form :deep(input.arco-input):-webkit-autofill,
|
||
.nova-login__form :deep(input.arco-input):-webkit-autofill:hover,
|
||
.nova-login__form :deep(input.arco-input):-webkit-autofill:focus,
|
||
.nova-login__form :deep(input.arco-input):-webkit-autofill:active {
|
||
-webkit-text-fill-color: #1e293b;
|
||
caret-color: #1e293b;
|
||
border: none;
|
||
/* 用大 inset 阴影盖住浏览器默认 autofill 背景 */
|
||
-webkit-box-shadow: 0 0 0 1000px #f0f3f4 inset !important;
|
||
box-shadow: 0 0 0 1000px #f0f3f4 inset !important;
|
||
transition: background-color 99999s ease-out 0s;
|
||
}
|
||
|
||
.nova-login__arco-input {
|
||
width: 100%;
|
||
}
|
||
|
||
.nova-login__field-error {
|
||
margin-top: 4px;
|
||
font-size: 13px;
|
||
color: #b91c1c;
|
||
}
|
||
|
||
.nova-login__error-box {
|
||
padding: 11px 14px;
|
||
font-size: 13px;
|
||
color: #b91c1c;
|
||
background: color-mix(in srgb, #fff1f2 72%, var(--app-acrylic-bg));
|
||
border: 1px solid color-mix(in srgb, #fecdd3 80%, var(--app-acrylic-border));
|
||
border-radius: 12px;
|
||
margin-bottom: 16px;
|
||
backdrop-filter: var(--app-acrylic-blur);
|
||
-webkit-backdrop-filter: var(--app-acrylic-blur);
|
||
}
|
||
|
||
.nova-login__submit {
|
||
width: 100%;
|
||
height: 52px;
|
||
margin-top: 4px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
border-radius: 14px;
|
||
background: var(--app-primary-color);
|
||
border: 1px solid color-mix(in srgb, var(--app-primary-color) 80%, transparent);
|
||
color: #fff;
|
||
letter-spacing: 0.5px;
|
||
cursor: pointer;
|
||
box-shadow: none;
|
||
transition: transform 0.2s, background 0.2s, opacity 0.2s;
|
||
font-family: inherit;
|
||
|
||
&:hover:not(:disabled) {
|
||
transform: translateY(-1px);
|
||
background: var(--app-primary-color-hover);
|
||
box-shadow: none;
|
||
}
|
||
|
||
&:active:not(:disabled) {
|
||
transform: translateY(1px);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
}
|
||
|
||
.agreement-area {
|
||
margin: 12px 0 0;
|
||
width: 100%;
|
||
min-width: 0;
|
||
container-type: inline-size;
|
||
container-name: login-agreement;
|
||
|
||
:deep(.arco-checkbox) {
|
||
max-width: 100%;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
/* 勾选框约 14px,首行行高 18px,与「我已阅读」光学对齐 */
|
||
:deep(.arco-checkbox-icon-hover) {
|
||
margin-top: 2px;
|
||
}
|
||
|
||
:deep(.arco-checkbox-label) {
|
||
flex: 1;
|
||
min-width: 0;
|
||
max-width: 100%;
|
||
color: #64748b;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex-wrap: wrap;
|
||
line-height: 18px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.agreement-line {
|
||
display: inline;
|
||
min-width: 0;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
line-height: 18px;
|
||
}
|
||
|
||
@supports (font-size: 1cqi) {
|
||
.agreement-line {
|
||
font-size: clamp(10px, 3.2cqi, 12px);
|
||
}
|
||
}
|
||
|
||
:deep(.arco-checkbox-checked .arco-checkbox-icon) {
|
||
background-color: var(--app-primary-color);
|
||
border-color: var(--app-primary-color);
|
||
}
|
||
|
||
:deep(.arco-checkbox-icon-hover::before) {
|
||
background-color: color-mix(in srgb, var(--app-primary-color) 12%, transparent);
|
||
}
|
||
|
||
:deep(.agreement-line .arco-link) {
|
||
color: var(--app-primary-color);
|
||
font-size: inherit;
|
||
font-weight: 600;
|
||
text-decoration: none;
|
||
border-radius: 4px;
|
||
padding: 0 2px;
|
||
|
||
&:hover {
|
||
color: var(--app-primary-color-hover);
|
||
background-color: color-mix(in srgb, var(--app-primary-color) 10%, transparent);
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.login-alt-entry {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 14px;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.login-alt-entry__divider {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
width: 100%;
|
||
font-size: 12px;
|
||
color: rgba(100, 116, 139, 0.75);
|
||
|
||
&::before,
|
||
&::after {
|
||
content: '';
|
||
flex: 1;
|
||
height: 1px;
|
||
background: rgba(148, 163, 184, 0.35);
|
||
}
|
||
}
|
||
|
||
.wechat-entry-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 48px;
|
||
height: 48px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: #07c160;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
transition:
|
||
background 0.2s cubic-bezier(0.22, 1, 0.36, 1),
|
||
transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
||
|
||
&:hover {
|
||
background: #06ad56;
|
||
color: #fff;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.96);
|
||
}
|
||
|
||
:deep(.arco-icon) {
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.login-alt-entry__hint {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
color: #86909c;
|
||
|
||
:deep(.arco-link) {
|
||
color: var(--app-primary-color);
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.agreement-content {
|
||
max-height: 55vh;
|
||
overflow-y: auto;
|
||
white-space: pre-wrap;
|
||
line-height: 1.8;
|
||
font-size: 12px;
|
||
color: #333;
|
||
}
|
||
</style>
|