672 lines
23 KiB
Vue
672 lines
23 KiB
Vue
<template>
|
||
<div class="nova-login">
|
||
<div class="nova-login__header">
|
||
<LoginLockLogo />
|
||
</div>
|
||
|
||
<form class="nova-login__form" autocomplete="off" @submit.prevent="handleSubmit">
|
||
<div class="nova-login__input-group">
|
||
<div class="nova-login__field-label">{{ t('login.field.account') }}</div>
|
||
<div class="nova-login__input-wrapper">
|
||
<span class="nova-login__prefix-icon" aria-hidden="true">
|
||
<icon-user :size="16" />
|
||
</span>
|
||
<div class="nova-login__input-shell">
|
||
<input
|
||
v-model="loginForm.account"
|
||
type="text"
|
||
class="nova-login__input"
|
||
:placeholder="t('login.form.account.placeholder')"
|
||
autocomplete="username"
|
||
@focus="onAccountFocus"
|
||
@blur="onAccountBlur"
|
||
@keydown.enter.prevent="focusPasswordInput"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<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>
|
||
<div class="nova-login__input-wrapper">
|
||
<span class="nova-login__prefix-icon" aria-hidden="true">
|
||
<icon-lock :size="16" />
|
||
</span>
|
||
<div class="nova-login__input-shell">
|
||
<input
|
||
ref="passwordInputRef"
|
||
v-model="loginForm.password"
|
||
:type="showPassword ? 'text' : 'password'"
|
||
class="nova-login__input"
|
||
:placeholder="t('login.form.password.placeholder')"
|
||
autocomplete="current-password"
|
||
@focus="onPasswordFocus"
|
||
@blur="onPasswordBlur"
|
||
@input="syncPasswordCharacterState"
|
||
@keydown.enter.prevent="handleSubmit"
|
||
/>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="nova-login__eye-toggle"
|
||
tabindex="-1"
|
||
:aria-label="showPassword ? t('login.hidePassword') : t('login.showPassword')"
|
||
@click="togglePasswordVisible"
|
||
>
|
||
<icon-eye-invisible v-if="!showPassword" :size="16" />
|
||
<icon-eye v-else :size="16" />
|
||
</button>
|
||
</div>
|
||
<div v-if="passwordError" class="nova-login__field-error">{{ passwordError }}</div>
|
||
</div>
|
||
|
||
<div v-if="errorBox" class="nova-login__error-box">{{ errorBox }}</div>
|
||
|
||
<div 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>
|
||
|
||
<button type="submit" class="nova-login__submit" :disabled="loading">
|
||
{{ loading ? t('login.submitting') : t('login.form.login') }}
|
||
</button>
|
||
</form>
|
||
|
||
<div v-if="props.loginType === OmsLoginType.Customer" class="wechat-login-entry">
|
||
<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="22" />
|
||
</button>
|
||
</a-tooltip>
|
||
</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 { useRouter } from 'vue-router';
|
||
import { useI18n } from 'vue-i18n';
|
||
import { Message } from '@arco-design/web-vue';
|
||
import {
|
||
IconUser,
|
||
IconLock,
|
||
IconWechat,
|
||
IconEye,
|
||
IconEyeInvisible,
|
||
} 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 { useUserStore } from '@/store';
|
||
import { getDefaultHomePathByType } from '@/utils/login-portal';
|
||
import { resolveAvatarUrl } from '@/utils/avatar';
|
||
import { enterSystemAfterLogin } from '@/utils/enter-system';
|
||
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 router = useRouter();
|
||
const userStore = useUserStore();
|
||
const rbacUserStore = useRbacUserStore();
|
||
|
||
const passwordInputRef = ref<HTMLInputElement | 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 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 togglePasswordVisible() {
|
||
showPassword.value = !showPassword.value;
|
||
syncPasswordCharacterState();
|
||
}
|
||
|
||
function onAccountFocus() {
|
||
accountError.value = '';
|
||
loginCharacters.value?.onAccountFocus();
|
||
}
|
||
|
||
function onAccountBlur() {
|
||
loginCharacters.value?.onAccountBlur();
|
||
}
|
||
|
||
function onPasswordFocus() {
|
||
passwordError.value = '';
|
||
loginCharacters.value?.onPasswordFocus();
|
||
}
|
||
|
||
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 = t('login.form.account.errMsg');
|
||
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 (!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);
|
||
}
|
||
|
||
const displayName =
|
||
userInfoData.name || userInfoData.account || fallbackAccount || String(userInfoData.id ?? '');
|
||
const userInfo: any = {
|
||
account: userInfoData.account || fallbackAccount,
|
||
name: displayName,
|
||
avatar: resolveAvatarUrl(userInfoData.avatar),
|
||
email: userInfoData.email,
|
||
id: userInfoData.id,
|
||
accountId: userInfoData.id,
|
||
defaultModule: undefined,
|
||
moduleList: [],
|
||
};
|
||
|
||
userStore.setInfo(userInfo);
|
||
|
||
const { redirect, ...othersQuery } = router.currentRoute.value.query;
|
||
const redirectStr = redirect as string | undefined;
|
||
const defaultHomePath = getDefaultHomePathByType(props.loginType);
|
||
const defaultTarget = { path: defaultHomePath };
|
||
|
||
const redirectParams: any = {
|
||
query: { ...othersQuery },
|
||
};
|
||
|
||
if (redirectStr) {
|
||
if (redirectStr.startsWith('/')) {
|
||
redirectParams.path = redirectStr;
|
||
delete redirectParams.name;
|
||
} else {
|
||
redirectParams.name = redirectStr;
|
||
delete redirectParams.path;
|
||
}
|
||
} else {
|
||
redirectParams.path = defaultTarget.path;
|
||
delete redirectParams.name;
|
||
}
|
||
|
||
Message.success(t('login.form.login.success'));
|
||
await nextTick();
|
||
await enterSystemAfterLogin({
|
||
target: redirectParams,
|
||
fallbackPath: defaultTarget.path,
|
||
playExit: () => loginExit?.playExitAnimation() ?? Promise.resolve(),
|
||
});
|
||
} else {
|
||
errorBox.value = t('login.form.login.failed');
|
||
Message.error(t('login.form.login.failed'));
|
||
loginCharacters.value?.onLoginFailed();
|
||
}
|
||
} catch {
|
||
loginCharacters.value?.onLoginFailed();
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.nova-login__header {
|
||
text-align: center;
|
||
margin-bottom: 28px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.nova-login__input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
min-width: 0;
|
||
height: 50px;
|
||
overflow: hidden;
|
||
background: rgba(248, 250, 252, 0.95);
|
||
border: 1px solid #d8dee8;
|
||
border-radius: 14px;
|
||
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
||
|
||
&:hover {
|
||
border-color: #14b8a6;
|
||
background: #fff;
|
||
}
|
||
|
||
&:focus-within {
|
||
border-color: #0f766e;
|
||
box-shadow: 0 0 0 4px rgba(20, 184, 166, 0.15);
|
||
background: #fff;
|
||
}
|
||
}
|
||
|
||
.nova-login__prefix-icon {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
padding-left: 14px;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.nova-login__input-shell {
|
||
flex: 1;
|
||
min-width: 0;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.nova-login__input-wrapper:focus-within .nova-login__prefix-icon {
|
||
color: #0f766e;
|
||
}
|
||
|
||
.nova-login__input {
|
||
width: 100%;
|
||
min-width: 0;
|
||
height: 100%;
|
||
border: none;
|
||
outline: none;
|
||
background: transparent;
|
||
font-size: 14px;
|
||
color: #111827;
|
||
padding: 0 12px 0 4px;
|
||
font-family: inherit;
|
||
box-sizing: border-box;
|
||
|
||
&::placeholder {
|
||
color: #9aa4b2;
|
||
}
|
||
|
||
&:-webkit-autofill,
|
||
&:-webkit-autofill:hover,
|
||
&:-webkit-autofill:focus,
|
||
&:-webkit-autofill:active {
|
||
-webkit-text-fill-color: #111827;
|
||
caret-color: #111827;
|
||
box-shadow: 0 0 0 1000px #f8fafc inset;
|
||
-webkit-box-shadow: 0 0 0 1000px #f8fafc inset;
|
||
transition: background-color 99999s ease-out 0s;
|
||
}
|
||
}
|
||
|
||
.nova-login__input-wrapper:focus-within .nova-login__input:-webkit-autofill,
|
||
.nova-login__input-wrapper:hover .nova-login__input:-webkit-autofill {
|
||
box-shadow: 0 0 0 1000px #fff inset;
|
||
-webkit-box-shadow: 0 0 0 1000px #fff inset;
|
||
}
|
||
|
||
.nova-login__eye-toggle {
|
||
display: flex;
|
||
flex-shrink: 0;
|
||
align-items: center;
|
||
padding-right: 14px;
|
||
color: #64748b;
|
||
cursor: pointer;
|
||
background: none;
|
||
border: none;
|
||
|
||
&:hover {
|
||
color: #0f766e;
|
||
}
|
||
}
|
||
|
||
.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: #fff1f2;
|
||
border: 1px solid #fecdd3;
|
||
border-radius: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.nova-login__submit {
|
||
width: 100%;
|
||
height: 52px;
|
||
margin-top: 4px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
border-radius: 14px;
|
||
background: #007979;
|
||
border: none;
|
||
color: #fff;
|
||
letter-spacing: 0.5px;
|
||
cursor: pointer;
|
||
box-shadow: 0 14px 26px rgba(0, 121, 121, 0.24);
|
||
transition: transform 0.2s, box-shadow 0.2s, background 0.2s, opacity 0.2s;
|
||
font-family: inherit;
|
||
|
||
&:hover:not(:disabled) {
|
||
transform: translateY(-1px);
|
||
background: #008f8f;
|
||
box-shadow: 0 16px 28px rgba(0, 121, 121, 0.32);
|
||
}
|
||
|
||
&:active:not(:disabled) {
|
||
transform: translateY(1px);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
}
|
||
|
||
.agreement-area {
|
||
margin: 0 0 16px;
|
||
width: 100%;
|
||
min-width: 0;
|
||
container-type: inline-size;
|
||
container-name: login-agreement;
|
||
|
||
:deep(.arco-checkbox) {
|
||
max-width: 100%;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
: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: 1.45;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.agreement-line {
|
||
display: inline;
|
||
min-width: 0;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
@supports (font-size: 1cqi) {
|
||
.agreement-line {
|
||
font-size: clamp(10px, 3.2cqi, 12px);
|
||
}
|
||
}
|
||
|
||
:deep(.agreement-line .arco-link) {
|
||
color: #0f766e;
|
||
font-size: inherit;
|
||
font-weight: 600;
|
||
text-decoration: none;
|
||
border-radius: 4px;
|
||
padding: 0 2px;
|
||
|
||
&:hover {
|
||
color: #14b8a6;
|
||
background-color: rgba(20, 184, 166, 0.1);
|
||
text-decoration: underline;
|
||
text-underline-offset: 2px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.wechat-login-entry {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.wechat-entry-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 44px;
|
||
height: 44px;
|
||
padding: 0;
|
||
border: 1px solid #d8dee8;
|
||
border-radius: 50%;
|
||
background: rgba(248, 250, 252, 0.95);
|
||
color: #07c160;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
transition: border-color 0.2s, background 0.2s;
|
||
|
||
&:hover {
|
||
border-color: #14b8a6;
|
||
background: #fff;
|
||
}
|
||
}
|
||
|
||
.agreement-content {
|
||
max-height: 55vh;
|
||
overflow-y: auto;
|
||
white-space: pre-wrap;
|
||
line-height: 1.8;
|
||
font-size: 12px;
|
||
color: #333;
|
||
}
|
||
</style>
|