928 lines
24 KiB
Vue
928 lines
24 KiB
Vue
<template>
|
||
<!-- Login Page v2 -->
|
||
<div class="login-page">
|
||
<!-- 顶部导航栏 -->
|
||
<div class="login-header-bar">
|
||
<div class="header-left">
|
||
<div class="logo-container">
|
||
<img v-if="logoUrl" :src="logoUrl" alt="Logo" class="logo-image" />
|
||
<Icon v-else name="home" size="lg" color="#31f08b" />
|
||
</div>
|
||
<span class="site-name">{{ siteName }}</span>
|
||
</div>
|
||
<div class="header-right">
|
||
<!-- 可以在这里添加其他按钮,如"帮助"等 -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 登录 / 注册 表单区域 -->
|
||
<div class="login-container">
|
||
<div class="login-box">
|
||
<h1 class="login-title">{{ activeTab === 'login' ? '登录' : '注册' }}</h1>
|
||
|
||
<!-- 登录表单 -->
|
||
<a-form
|
||
v-if="activeTab === 'login'"
|
||
:model="loginForm"
|
||
:rules="loginRules"
|
||
@finish="handleLogin"
|
||
layout="vertical"
|
||
class="login-form"
|
||
>
|
||
<a-form-item name="username">
|
||
<a-input
|
||
v-model:value="loginForm.username"
|
||
size="large"
|
||
placeholder="请输入用户名"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="password">
|
||
<a-input-password
|
||
v-model:value="loginForm.password"
|
||
size="large"
|
||
placeholder="请输入密码"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<div class="agreement-checkbox">
|
||
<a-checkbox v-model:checked="agreed">
|
||
<span class="agreement-text">
|
||
我已阅读并同意
|
||
<a href="#" class="agreement-link">服务条款</a>
|
||
和
|
||
<a href="#" class="agreement-link">隐私协议</a>
|
||
</span>
|
||
</a-checkbox>
|
||
</div>
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<a-button
|
||
type="primary"
|
||
size="large"
|
||
block
|
||
:loading="loading"
|
||
:disabled="!agreed"
|
||
html-type="submit"
|
||
class="login-button"
|
||
>
|
||
{{ loading ? '登录中...' : '登录' }}
|
||
</a-button>
|
||
</a-form-item>
|
||
|
||
<div class="auth-switch">
|
||
<span class="switch-text">还没有账号?</span>
|
||
<button type="button" class="switch-link" @click="activeTab = 'register'">
|
||
注册账号
|
||
</button>
|
||
</div>
|
||
</a-form>
|
||
|
||
<!-- 注册表单 -->
|
||
<a-form
|
||
v-else
|
||
:model="registerForm"
|
||
:rules="registerRules"
|
||
@finish="handleRegister"
|
||
layout="vertical"
|
||
class="login-form register-form"
|
||
>
|
||
<a-form-item name="realName">
|
||
<a-input
|
||
v-model:value="registerForm.realName"
|
||
size="large"
|
||
placeholder="请输入公司花名"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="username">
|
||
<a-input
|
||
v-model:value="registerForm.username"
|
||
size="large"
|
||
placeholder="请输入用户名"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="password">
|
||
<a-input-password
|
||
v-model:value="registerForm.password"
|
||
size="large"
|
||
placeholder="请输入密码"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="confirmPassword">
|
||
<a-input-password
|
||
v-model:value="registerForm.confirmPassword"
|
||
size="large"
|
||
placeholder="请再次输入密码"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="roleId" label="选择角色" class="form-item-with-label">
|
||
<a-radio-group v-model:value="registerForm.roleId" size="large" class="role-radio-group">
|
||
<a-radio :value="6" class="role-radio">
|
||
<Icon name="users" size="sm" style="margin-right: 4px; vertical-align: middle;" />
|
||
需求方
|
||
</a-radio>
|
||
<a-radio :value="5" class="role-radio">
|
||
<Icon name="paintBrush" size="sm" style="margin-right: 4px; vertical-align: middle;" />
|
||
设计师
|
||
</a-radio>
|
||
</a-radio-group>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="deptId" label="选择部门" class="form-item-with-label">
|
||
<a-select
|
||
v-model:value="registerForm.deptId"
|
||
size="large"
|
||
placeholder="请选择部门"
|
||
class="login-select"
|
||
:options="filteredDepartments"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="email">
|
||
<a-input
|
||
v-model:value="registerForm.email"
|
||
size="large"
|
||
placeholder="请输入邮箱(选填)"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item name="phone">
|
||
<a-input
|
||
v-model:value="registerForm.phone"
|
||
size="large"
|
||
placeholder="请输入手机号(选填)"
|
||
class="login-input"
|
||
/>
|
||
</a-form-item>
|
||
|
||
<a-form-item>
|
||
<a-button
|
||
type="primary"
|
||
size="large"
|
||
block
|
||
:loading="registerLoading"
|
||
html-type="submit"
|
||
class="login-button"
|
||
>
|
||
{{ registerLoading ? '注册中...' : '注册' }}
|
||
</a-button>
|
||
</a-form-item>
|
||
|
||
<div class="auth-switch">
|
||
<span class="switch-text">已经有账号?</span>
|
||
<button type="button" class="switch-link" @click="activeTab = 'login'">
|
||
返回登录
|
||
</button>
|
||
</div>
|
||
</a-form>
|
||
|
||
<!-- 其他登录方式 -->
|
||
<div v-if="activeTab === 'login'" class="alternative-login">
|
||
<a-button
|
||
v-if="dingTalkLoginEnabled"
|
||
block
|
||
class="alt-login-button dingtalk-login-button"
|
||
@click="handleDingTalkLogin"
|
||
:loading="dingTalkLoginLoading"
|
||
>
|
||
<img
|
||
src="https://img.alicdn.com/imgextra/i1/O1CN01CHK9VA1YTzZJxXGf7_!!6000000003022-2-tps-400-400.png"
|
||
alt="钉钉"
|
||
class="dingtalk-logo"
|
||
onerror="this.style.display='none'; this.nextSibling.style.display='inline-block';"
|
||
/>
|
||
<span class="dingtalk-logo-text" style="display: none;">D</span>
|
||
钉钉登录
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, reactive, ref, onMounted, onUnmounted, watch } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useAuthStore } from '@/stores/auth'
|
||
import { useSiteSettingsStore } from '@/stores/siteSettings'
|
||
import Icon from '@/components/Icon.vue'
|
||
import type { Rule } from 'ant-design-vue/es/form'
|
||
import { message } from 'ant-design-vue'
|
||
import { register as registerApi } from '@/api/auth'
|
||
import * as departmentApi from '@/api/department'
|
||
import { isDingTalkFeatureEnabled } from '@/api/dingtalk'
|
||
import request from '@/utils/request'
|
||
import type { Result } from '@/api/types'
|
||
|
||
const router = useRouter()
|
||
const authStore = useAuthStore()
|
||
const siteSettingsStore = useSiteSettingsStore()
|
||
|
||
const activeTab = ref<'login' | 'register'>('login')
|
||
|
||
const loading = ref(false)
|
||
const registerLoading = ref(false)
|
||
const agreed = ref(false)
|
||
|
||
const loginForm = reactive({
|
||
username: '',
|
||
password: ''
|
||
})
|
||
|
||
const registerForm = reactive({
|
||
username: '',
|
||
password: '',
|
||
confirmPassword: '',
|
||
realName: '',
|
||
roleId: 6, // 默认为需求方
|
||
deptId: undefined as number | undefined,
|
||
email: '',
|
||
phone: ''
|
||
})
|
||
|
||
// 钉钉登录
|
||
const dingTalkLoginEnabled = ref(false)
|
||
const dingTalkLoginLoading = ref(false)
|
||
|
||
// 部门列表
|
||
const departments = ref<any[]>([])
|
||
const filteredDepartments = computed(() => {
|
||
if (!registerForm.roleId) return []
|
||
|
||
const deptType = registerForm.roleId === 5 ? 'receiving' : 'demand'
|
||
return departments.value
|
||
.filter(dept => dept.deptType === deptType)
|
||
.map(dept => ({
|
||
value: dept.id,
|
||
label: dept.deptName
|
||
}))
|
||
})
|
||
|
||
const loginRules: Record<string, Rule[]> = {
|
||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
|
||
}
|
||
|
||
const registerRules: Record<string, Rule[]> = {
|
||
realName: [{ required: true, message: '请输入公司花名', trigger: 'blur' }],
|
||
username: [
|
||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||
{ min: 3, max: 20, message: '用户名长度为3-20个字符', trigger: 'blur' }
|
||
],
|
||
password: [
|
||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||
{ min: 6, message: '密码至少6个字符', trigger: 'blur' }
|
||
],
|
||
confirmPassword: [
|
||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||
{
|
||
validator: (_rule: Rule, value: string) => {
|
||
if (!value) {
|
||
return Promise.reject('请再次输入密码')
|
||
}
|
||
if (value !== registerForm.password) {
|
||
return Promise.reject('两次输入的密码不一致')
|
||
}
|
||
return Promise.resolve()
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
roleId: [{ required: true, message: '请选择角色', trigger: 'change' }],
|
||
deptId: [{ required: true, message: '请选择部门', trigger: 'change' }]
|
||
}
|
||
|
||
// 监听角色变化,自动清空部门选择
|
||
watch(() => registerForm.roleId, () => {
|
||
registerForm.deptId = undefined
|
||
})
|
||
|
||
// 使用全局store的网站设置
|
||
const siteName = computed(() => siteSettingsStore.siteName)
|
||
const logoUrl = computed(() => siteSettingsStore.logoUrl)
|
||
|
||
// 监听网站设置更新
|
||
const handleSiteSettingsUpdate = (event: Event) => {
|
||
const customEvent = event as CustomEvent
|
||
if (customEvent.detail) {
|
||
siteSettingsStore.updateSiteSettings(
|
||
customEvent.detail.siteName || 'DesignHub',
|
||
customEvent.detail.logoUrl || ''
|
||
)
|
||
}
|
||
}
|
||
|
||
const handleLogin = async () => {
|
||
loading.value = true
|
||
try {
|
||
const success = await authStore.login(loginForm.username, loginForm.password)
|
||
if (success) {
|
||
// 登录成功,跳转到首页
|
||
router.push('/')
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const handleRegister = async () => {
|
||
registerLoading.value = true
|
||
try {
|
||
const res = await registerApi({
|
||
username: registerForm.username,
|
||
password: registerForm.password,
|
||
realName: registerForm.realName,
|
||
roleId: registerForm.roleId!,
|
||
deptId: registerForm.deptId!,
|
||
email: registerForm.email,
|
||
phone: registerForm.phone
|
||
})
|
||
|
||
if (res.code === 200) {
|
||
message.success('注册成功,请使用新账号登录')
|
||
// 切换回登录标签,并自动填充账号密码
|
||
activeTab.value = 'login'
|
||
loginForm.username = registerForm.username
|
||
loginForm.password = registerForm.password
|
||
} else {
|
||
message.error(res.message || '注册失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('注册失败:', error)
|
||
message.error(error.message || '注册失败')
|
||
} finally {
|
||
registerLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载部门列表
|
||
const loadDepartments = async () => {
|
||
try {
|
||
const res = await departmentApi.getDepartmentList()
|
||
if (res.code === 200 && res.data) {
|
||
departments.value = res.data
|
||
console.log('部门列表加载成功:', departments.value)
|
||
}
|
||
} catch (error) {
|
||
console.error('加载部门列表失败:', error)
|
||
message.error('加载部门列表失败,请刷新页面重试')
|
||
}
|
||
}
|
||
|
||
// 检查钉钉登录是否启用
|
||
const checkDingTalkLogin = async () => {
|
||
try {
|
||
dingTalkLoginEnabled.value = await isDingTalkFeatureEnabled('login')
|
||
console.log('钉钉登录状态:', dingTalkLoginEnabled.value)
|
||
} catch (error) {
|
||
console.error('检查钉钉登录状态失败:', error)
|
||
dingTalkLoginEnabled.value = false
|
||
}
|
||
}
|
||
|
||
// 处理钉钉登录
|
||
const handleDingTalkLogin = async () => {
|
||
dingTalkLoginLoading.value = true
|
||
try {
|
||
// 获取钉钉登录URL
|
||
// 注意:request 拦截器已经处理了 Result 包装,返回的就是 Result 对象
|
||
const response = await request.get<Result<{ authUrl: string; appKey: string }>>('/dingtalk/oauth/login', {
|
||
params: {
|
||
redirectUrl: `${window.location.origin}/auth/dingtalk/callback`
|
||
}
|
||
})
|
||
|
||
// response 已经是 Result 对象,直接使用 response.data
|
||
if (response.code === 200 && response.data) {
|
||
// 跳转到钉钉登录页面
|
||
window.location.href = response.data.authUrl
|
||
} else {
|
||
message.error(response.message || '获取钉钉登录地址失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('钉钉登录失败:', error)
|
||
message.error(error.response?.data?.message || error.message || '钉钉登录失败,请稍后重试')
|
||
} finally {
|
||
dingTalkLoginLoading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 加载网站设置
|
||
siteSettingsStore.loadSiteSettings()
|
||
|
||
// 加载部门列表
|
||
loadDepartments()
|
||
|
||
// 检查钉钉登录是否启用
|
||
checkDingTalkLogin()
|
||
|
||
// 监听网站设置更新事件(在加载后监听,确保能接收到事件)
|
||
window.addEventListener('siteSettingsUpdated', handleSiteSettingsUpdate)
|
||
|
||
// 监听storage事件(跨标签页同步)
|
||
window.addEventListener('storage', (e) => {
|
||
if (e.key === 'siteName' || e.key === 'logoUrl') {
|
||
siteSettingsStore.loadSiteSettings()
|
||
}
|
||
})
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
window.removeEventListener('siteSettingsUpdated', handleSiteSettingsUpdate)
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.login-page {
|
||
min-height: 100vh;
|
||
background: $bg-primary;
|
||
position: relative;
|
||
}
|
||
|
||
// 顶部导航栏
|
||
.login-header-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: $space-lg $space-xl;
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 10;
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: $space-md;
|
||
|
||
.logo-container {
|
||
width: 60px;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.logo-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
}
|
||
}
|
||
|
||
.site-name {
|
||
font-size: $font-size-2xl;
|
||
font-weight: $font-weight-semibold;
|
||
color: $text-white;
|
||
line-height: 60px;
|
||
height: 60px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.header-right {
|
||
// 可以在这里添加其他按钮
|
||
}
|
||
}
|
||
|
||
// 登录容器
|
||
.login-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 100vh;
|
||
padding: $space-3xl $space-lg;
|
||
}
|
||
|
||
.login-box {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
animation: slideUp 0.4s ease-out;
|
||
}
|
||
|
||
@keyframes slideUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.login-title {
|
||
font-size: $font-size-4xl;
|
||
font-weight: $font-weight-bold;
|
||
color: $text-white;
|
||
margin-bottom: $space-3xl;
|
||
text-align: left;
|
||
}
|
||
|
||
.login-form {
|
||
margin-bottom: $space-xl;
|
||
|
||
:deep(.ant-form-item) {
|
||
margin-bottom: $space-md;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
|
||
:deep(.ant-form-item-label) {
|
||
display: none;
|
||
}
|
||
|
||
// 注册表单需要显示label
|
||
&.register-form {
|
||
:deep(.form-item-with-label) {
|
||
.ant-form-item-label {
|
||
display: block;
|
||
padding-bottom: 8px;
|
||
|
||
label {
|
||
color: $text-white;
|
||
font-size: $font-size-base;
|
||
font-weight: $font-weight-medium;
|
||
|
||
&::before {
|
||
color: $brand-primary !important;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.login-input {
|
||
height: 48px;
|
||
line-height: 48px;
|
||
background: $bg-secondary !important;
|
||
border: 1px solid $border-color !important;
|
||
border-radius: $radius-md;
|
||
color: $text-white !important;
|
||
font-size: $font-size-base;
|
||
transition: all 0.2s ease;
|
||
text-align: left;
|
||
padding: 0 12px;
|
||
box-sizing: border-box;
|
||
|
||
&::placeholder {
|
||
color: $text-tertiary !important;
|
||
line-height: normal;
|
||
}
|
||
|
||
&:hover {
|
||
background: $bg-secondary !important;
|
||
border-color: rgba($brand-primary, 0.5) !important;
|
||
color: $text-white !important;
|
||
}
|
||
|
||
&:focus,
|
||
&.ant-input-focused,
|
||
&:focus-visible {
|
||
background: $bg-secondary !important;
|
||
border-color: $brand-primary !important;
|
||
box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important;
|
||
color: $text-white !important;
|
||
outline: none !important;
|
||
}
|
||
}
|
||
|
||
:deep(.ant-input-password) {
|
||
background: $bg-secondary !important;
|
||
border: 1px solid $border-color !important;
|
||
border-radius: $radius-md !important;
|
||
height: 48px !important;
|
||
width: 100% !important;
|
||
box-shadow: none !important;
|
||
transition: all 0.2s ease;
|
||
padding: 0 !important;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.ant-input {
|
||
background: transparent !important;
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
color: $text-white !important;
|
||
height: 48px;
|
||
line-height: 48px;
|
||
font-size: $font-size-base;
|
||
padding: 0 40px 0 12px !important;
|
||
text-align: left;
|
||
flex: 1;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
|
||
&::placeholder {
|
||
color: $text-tertiary !important;
|
||
line-height: normal;
|
||
}
|
||
|
||
&:hover {
|
||
background: transparent !important;
|
||
color: $text-white !important;
|
||
}
|
||
|
||
&:focus,
|
||
&:focus-visible {
|
||
background: transparent !important;
|
||
color: $text-white !important;
|
||
outline: none !important;
|
||
box-shadow: none !important;
|
||
}
|
||
}
|
||
|
||
.ant-input-password-icon {
|
||
color: $text-secondary;
|
||
right: 12px;
|
||
|
||
&:hover {
|
||
color: $text-primary;
|
||
}
|
||
}
|
||
|
||
&:hover {
|
||
background: $bg-secondary !important;
|
||
border-color: rgba($brand-primary, 0.5) !important;
|
||
}
|
||
|
||
&.ant-input-password-focused,
|
||
&:focus,
|
||
&:focus-visible {
|
||
background: $bg-secondary !important;
|
||
border-color: $brand-primary !important;
|
||
box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important;
|
||
outline: none !important;
|
||
}
|
||
}
|
||
|
||
.agreement-checkbox {
|
||
margin-bottom: $space-md;
|
||
|
||
:deep(.ant-checkbox-wrapper) {
|
||
color: $text-white;
|
||
font-size: $font-size-sm;
|
||
|
||
.ant-checkbox {
|
||
.ant-checkbox-inner {
|
||
background-color: $bg-secondary;
|
||
border-color: $border-color;
|
||
}
|
||
|
||
&.ant-checkbox-checked .ant-checkbox-inner {
|
||
background-color: $brand-primary;
|
||
border-color: $brand-primary;
|
||
}
|
||
}
|
||
}
|
||
|
||
.agreement-text {
|
||
color: $text-white;
|
||
font-size: $font-size-sm;
|
||
}
|
||
|
||
.agreement-link {
|
||
color: $brand-primary;
|
||
text-decoration: none;
|
||
transition: color 0.2s ease;
|
||
|
||
&:hover {
|
||
color: $brand-hover;
|
||
text-decoration: underline;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 下拉选择框样式
|
||
.login-select {
|
||
height: 48px;
|
||
|
||
:deep(.ant-select-selector) {
|
||
height: 48px !important;
|
||
background: $bg-secondary !important;
|
||
border: 1px solid $border-color !important;
|
||
border-radius: $radius-md !important;
|
||
color: $text-white !important;
|
||
padding: 0 12px !important;
|
||
|
||
.ant-select-selection-item,
|
||
.ant-select-selection-placeholder {
|
||
line-height: 46px !important;
|
||
color: $text-white !important;
|
||
}
|
||
|
||
.ant-select-selection-placeholder {
|
||
color: $text-tertiary !important;
|
||
}
|
||
}
|
||
|
||
&:hover :deep(.ant-select-selector) {
|
||
border-color: rgba($brand-primary, 0.5) !important;
|
||
}
|
||
|
||
:deep(.ant-select-focused .ant-select-selector) {
|
||
border-color: $brand-primary !important;
|
||
box-shadow: 0 0 0 2px rgba($brand-primary, 0.1) !important;
|
||
}
|
||
|
||
:deep(.ant-select-arrow) {
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
|
||
// 角色单选按钮组
|
||
.role-radio-group {
|
||
display: flex;
|
||
gap: 16px;
|
||
|
||
:deep(.ant-radio-wrapper) {
|
||
flex: 1;
|
||
padding: 12px 16px;
|
||
background: $bg-secondary;
|
||
border: 1px solid $border-color;
|
||
border-radius: $radius-md;
|
||
transition: all 0.2s ease;
|
||
color: $text-white;
|
||
|
||
&:hover {
|
||
border-color: rgba($brand-primary, 0.5);
|
||
background: rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.ant-radio {
|
||
.ant-radio-inner {
|
||
background: $bg-tertiary;
|
||
border-color: $border-color;
|
||
}
|
||
|
||
&.ant-radio-checked .ant-radio-inner {
|
||
background: $brand-primary;
|
||
border-color: $brand-primary;
|
||
|
||
&::after {
|
||
background-color: #1a1d23;
|
||
}
|
||
}
|
||
}
|
||
|
||
&.ant-radio-wrapper-checked {
|
||
border-color: $brand-primary;
|
||
background: rgba($brand-primary, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.login-button {
|
||
height: 48px;
|
||
background: $brand-primary !important;
|
||
border: none !important;
|
||
border-radius: $radius-md;
|
||
font-size: $font-size-base;
|
||
font-weight: $font-weight-semibold;
|
||
color: #000000 !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
text-align: center !important;
|
||
transition: all 0.2s ease;
|
||
|
||
:deep(span) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
}
|
||
|
||
&:hover:not(:disabled) {
|
||
background: $brand-hover !important;
|
||
border-color: $brand-hover !important;
|
||
color: #000000 !important;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 12px rgba($brand-primary, 0.3);
|
||
}
|
||
|
||
&:active:not(:disabled) {
|
||
background: $brand-pressed !important;
|
||
border-color: $brand-pressed !important;
|
||
color: #000000 !important;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
&:focus:not(:disabled) {
|
||
background: $brand-primary !important;
|
||
border-color: $brand-primary !important;
|
||
color: #000000 !important;
|
||
box-shadow: 0 0 0 2px rgba($brand-primary, 0.2);
|
||
}
|
||
|
||
&:disabled {
|
||
background: rgba($brand-primary, 0.7) !important;
|
||
border-color: rgba($brand-primary, 0.7) !important;
|
||
color: #000000 !important;
|
||
box-shadow: none;
|
||
cursor: not-allowed;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
|
||
.auth-switch {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $space-sm;
|
||
margin-top: $space-md;
|
||
|
||
.switch-text {
|
||
color: $text-secondary;
|
||
font-size: $font-size-sm;
|
||
}
|
||
|
||
.switch-link {
|
||
padding: 6px 18px;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba($brand-primary, 0.6);
|
||
background: rgba($brand-primary, 0.1);
|
||
color: $brand-primary;
|
||
font-size: $font-size-sm;
|
||
font-weight: $font-weight-semibold;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
background: rgba($brand-primary, 0.2);
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 6px 16px rgba($brand-primary, 0.25);
|
||
}
|
||
|
||
&:active {
|
||
transform: translateY(0);
|
||
background: rgba($brand-primary, 0.25);
|
||
}
|
||
}
|
||
}
|
||
|
||
.alternative-login {
|
||
margin-top: $space-lg;
|
||
|
||
.alt-login-button {
|
||
height: 48px;
|
||
background: $bg-secondary;
|
||
border: 1px solid $border-color;
|
||
border-radius: $radius-md;
|
||
color: $text-white;
|
||
font-size: $font-size-base;
|
||
transition: all 0.2s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&:hover:not(:disabled) {
|
||
background: $bg-tertiary;
|
||
border-color: rgba($brand-primary, 0.3);
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
|
||
.dingtalk-login-button {
|
||
background: #0070f3;
|
||
border-color: #0070f3;
|
||
color: #ffffff;
|
||
|
||
&:hover:not(:disabled) {
|
||
background: #0051cc;
|
||
border-color: #0051cc;
|
||
}
|
||
|
||
.dingtalk-logo {
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-right: 8px;
|
||
vertical-align: middle;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
|