add(refreshTokenService):完善刷新令牌服务
This commit is contained in:
@@ -29,26 +29,21 @@
|
||||
<div class="content-header">
|
||||
<h2>{{ getCurrentTabTitle() }}</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search search-icon"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索..."
|
||||
v-model="searchText"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<input type="text" placeholder="搜索..." v-model="searchText" @input="handleSearch" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-content">
|
||||
<!-- 消息 -->
|
||||
<div v-if="currentTab === 'chat'" class="chat-list">
|
||||
<ul>
|
||||
<li
|
||||
v-for="chat in filteredChats"
|
||||
:key="chat.id"
|
||||
@click="selectChat(chat)"
|
||||
:class="{active: currentChat?.id === chat.id}"
|
||||
<li
|
||||
v-for="chat in filteredChats"
|
||||
:key="chat.id"
|
||||
@click="selectChat(chat)"
|
||||
:class="{ active: currentChat?.id === chat.id }"
|
||||
>
|
||||
<div class="chat-avatar">{{ getInitials(chat.name) }}</div>
|
||||
<div class="info">
|
||||
@@ -125,12 +120,14 @@
|
||||
</header>
|
||||
|
||||
<div class="chat-body" ref="chatBody">
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
:class="['msg', msg.from === 'me' ? 'me' : 'them']"
|
||||
>
|
||||
<div class="msg-avatar">{{ getInitials(msg.from === 'me' ? '我' : currentChat.name) }}</div>
|
||||
<div class="msg-avatar">
|
||||
{{ getInitials(msg.from === 'me' ? '我' : currentChat.name) }}
|
||||
</div>
|
||||
<div class="msg-content">
|
||||
<div class="bubble">{{ msg.text }}</div>
|
||||
<div class="msg-time">{{ msg.time }}</div>
|
||||
@@ -140,10 +137,10 @@
|
||||
|
||||
<footer class="chat-input">
|
||||
<div class="input-area">
|
||||
<textarea
|
||||
v-model="input"
|
||||
placeholder="输入消息..."
|
||||
@keydown.enter.prevent="send"
|
||||
<textarea
|
||||
v-model="input"
|
||||
placeholder="输入消息..."
|
||||
@keydown.enter.prevent="send"
|
||||
rows="1"
|
||||
ref="messageInput"
|
||||
></textarea>
|
||||
@@ -153,10 +150,13 @@
|
||||
</div>
|
||||
</footer>
|
||||
</aside>
|
||||
|
||||
|
||||
<!-- 空聊天状态 -->
|
||||
<aside class="chat-area" v-else>
|
||||
<div class="empty-state" style="height: 100%; display: flex; flex-direction: column; justify-content: center;">
|
||||
<div
|
||||
class="empty-state"
|
||||
style="height: 100%; display: flex; flex-direction: column; justify-content: center"
|
||||
>
|
||||
<i class="fas fa-comments icon"></i>
|
||||
<h3>选择一个对话开始聊天</h3>
|
||||
<p>在左侧列表中选择联系人开始对话</p>
|
||||
@@ -172,18 +172,32 @@ import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
const menus = ref([
|
||||
{ key: 'chat', label: '消息', icon: 'fas fa-comment-dots', notification: 3 },
|
||||
{ key: 'friends', label: '联系人', icon: 'fas fa-user-friends' },
|
||||
{ key: 'groups', label: '群聊', icon: 'fas fa-users' }
|
||||
{ key: 'groups', label: '群聊', icon: 'fas fa-users' },
|
||||
])
|
||||
|
||||
const currentTab = ref('chat')
|
||||
const searchText = ref('')
|
||||
|
||||
const chats = ref([
|
||||
{ id: 1, name: '张三', lastMsg: '今晚一起吃饭吗?', lastTime: '10:30', unread: 2, status: 'online' },
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
lastMsg: '今晚一起吃饭吗?',
|
||||
lastTime: '10:30',
|
||||
unread: 2,
|
||||
status: 'online',
|
||||
},
|
||||
{ id: 2, name: '李四', lastMsg: '收到文件了吗?', lastTime: '昨天', unread: 0, status: 'online' },
|
||||
{ id: 3, name: '王五', lastMsg: '项目进展如何?', lastTime: '周三', unread: 1, status: 'offline' },
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
lastMsg: '项目进展如何?',
|
||||
lastTime: '周三',
|
||||
unread: 1,
|
||||
status: 'offline',
|
||||
},
|
||||
{ id: 4, name: '赵六', lastMsg: '周末有空吗?', lastTime: '周一', unread: 0, status: 'online' },
|
||||
{ id: 5, name: '钱七', lastMsg: '会议改期了', lastTime: '3月15日', unread: 0, status: 'online' }
|
||||
{ id: 5, name: '钱七', lastMsg: '会议改期了', lastTime: '3月15日', unread: 0, status: 'online' },
|
||||
])
|
||||
|
||||
const friends = ref([
|
||||
@@ -193,7 +207,7 @@ const friends = ref([
|
||||
{ id: 4, name: '赵六', status: 'online' },
|
||||
{ id: 5, name: '钱七', status: 'online' },
|
||||
{ id: 6, name: '孙八', status: 'online' },
|
||||
{ id: 7, name: '周九', status: 'offline' }
|
||||
{ id: 7, name: '周九', status: 'offline' },
|
||||
])
|
||||
|
||||
const currentChat = ref(null)
|
||||
@@ -205,22 +219,23 @@ const messageInput = ref(null)
|
||||
// 计算属性
|
||||
const filteredChats = computed(() => {
|
||||
if (!searchText.value) return chats.value
|
||||
return chats.value.filter(chat =>
|
||||
chat.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
chat.lastMsg.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
return chats.value.filter(
|
||||
(chat) =>
|
||||
chat.name.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
chat.lastMsg.toLowerCase().includes(searchText.value.toLowerCase()),
|
||||
)
|
||||
})
|
||||
|
||||
const filteredFriends = computed(() => {
|
||||
if (!searchText.value) return friends.value
|
||||
return friends.value.filter(friend =>
|
||||
friend.name.toLowerCase().includes(searchText.value.toLowerCase())
|
||||
return friends.value.filter((friend) =>
|
||||
friend.name.toLowerCase().includes(searchText.value.toLowerCase()),
|
||||
)
|
||||
})
|
||||
|
||||
// 方法
|
||||
function getCurrentTabTitle() {
|
||||
const menu = menus.value.find(m => m.key === currentTab.value)
|
||||
const menu = menus.value.find((m) => m.key === currentTab.value)
|
||||
return menu ? menu.label : '消息'
|
||||
}
|
||||
|
||||
@@ -236,25 +251,23 @@ function formatTime(date) {
|
||||
// 模拟回复
|
||||
function simulateReply() {
|
||||
if (!currentChat.value) return
|
||||
|
||||
const replies = [
|
||||
'好的,我明白了',
|
||||
'听起来不错',
|
||||
'让我考虑一下',
|
||||
'没问题,就这么办'
|
||||
]
|
||||
|
||||
|
||||
const replies = ['好的,我明白了', '听起来不错', '让我考虑一下', '没问题,就这么办']
|
||||
|
||||
const randomReply = replies[Math.floor(Math.random() * replies.length)]
|
||||
|
||||
setTimeout(() => {
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
from: 'them',
|
||||
text: randomReply,
|
||||
time: formatTime(new Date())
|
||||
})
|
||||
nextTick(scrollBottom)
|
||||
}, 1000 + Math.random() * 2000)
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
from: 'them',
|
||||
text: randomReply,
|
||||
time: formatTime(new Date()),
|
||||
})
|
||||
nextTick(scrollBottom)
|
||||
},
|
||||
1000 + Math.random() * 2000,
|
||||
)
|
||||
}
|
||||
|
||||
function selectChat(chat) {
|
||||
@@ -263,13 +276,13 @@ function selectChat(chat) {
|
||||
{ id: 1, from: 'them', text: `你好,我是 ${chat.name},很高兴认识你!`, time: '09:15' },
|
||||
{ id: 2, from: 'me', text: '你好呀,也很高兴认识你~', time: '09:16' },
|
||||
{ id: 3, from: 'them', text: '最近在忙什么呢?', time: '09:20' },
|
||||
{ id: 4, from: 'me', text: '在做一个新项目,挺有意思的。', time: '09:22' }
|
||||
{ id: 4, from: 'me', text: '在做一个新项目,挺有意思的。', time: '09:22' },
|
||||
]
|
||||
nextTick(() => {
|
||||
scrollBottom()
|
||||
focusInput()
|
||||
})
|
||||
|
||||
|
||||
// 清除未读消息
|
||||
if (chat.unread) {
|
||||
chat.unread = 0
|
||||
@@ -279,16 +292,16 @@ function selectChat(chat) {
|
||||
|
||||
function send() {
|
||||
if (!input.value.trim()) return
|
||||
|
||||
|
||||
messages.value.push({
|
||||
id: Date.now(),
|
||||
from: 'me',
|
||||
text: input.value,
|
||||
time: formatTime(new Date())
|
||||
time: formatTime(new Date()),
|
||||
})
|
||||
input.value = ''
|
||||
nextTick(scrollBottom)
|
||||
|
||||
|
||||
// 模拟回复
|
||||
simulateReply()
|
||||
}
|
||||
@@ -320,7 +333,7 @@ onMounted(() => {
|
||||
if (chats.value.length > 0) {
|
||||
selectChat(chats.value[0])
|
||||
}
|
||||
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
@@ -483,13 +496,15 @@ onUnmounted(() => {
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
|
||||
.chat-list ul, .friend-list ul {
|
||||
.chat-list ul,
|
||||
.friend-list ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chat-list li, .friend-list li {
|
||||
.chat-list li,
|
||||
.friend-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -500,7 +515,8 @@ onUnmounted(() => {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.chat-list li:hover, .friend-list li:hover {
|
||||
.chat-list li:hover,
|
||||
.friend-list li:hover {
|
||||
background: #f7fafc;
|
||||
}
|
||||
|
||||
@@ -529,7 +545,9 @@ onUnmounted(() => {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.name-row, .last-row, .status-row {
|
||||
.name-row,
|
||||
.last-row,
|
||||
.status-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -809,7 +827,7 @@ onUnmounted(() => {
|
||||
.im-container {
|
||||
min-width: 700px;
|
||||
}
|
||||
|
||||
|
||||
.chat-area {
|
||||
width: 350px;
|
||||
min-width: 300px;
|
||||
@@ -820,10 +838,10 @@ onUnmounted(() => {
|
||||
.im-container {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
|
||||
.chat-area {
|
||||
width: 300px;
|
||||
min-width: 250px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,10 +6,22 @@
|
||||
<div class="brand">
|
||||
<div class="brand-logo" aria-hidden="true">
|
||||
<!-- 圆形头像徽标 -->
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="2" y="2" width="20" height="20" rx="5" fill="currentColor" />
|
||||
<path d="M8 14c0-1.657 1.343-3 3-3s3 1.343 3 3" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="11" cy="9" r="1.3" fill="white"/>
|
||||
<path
|
||||
d="M8 14c0-1.657 1.343-3 3-3s3 1.343 3 3"
|
||||
stroke="white"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="11" cy="9" r="1.3" fill="white" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="brand-name">即时通讯</div>
|
||||
@@ -34,19 +46,55 @@
|
||||
|
||||
<form class="login-form" @submit.prevent="handleLogin" autocomplete="on" novalidate>
|
||||
<div class="input-container" :class="{ focused: usernameFocused || username }">
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M20 21V19C20 16.7909 18.2091 15 16 15H8C5.79086 15 4 16.7909 4 19V21" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
<circle cx="12" cy="7" r="4" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
d="M20 21V19C20 16.7909 18.2091 15 16 15H8C5.79086 15 4 16.7909 4 19V21"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.6"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<circle cx="12" cy="7" r="4" fill="none" stroke="currentColor" stroke-width="1.6" />
|
||||
</svg>
|
||||
<input id="username" type="text" v-model="username" @focus="usernameFocused = true" @blur="usernameFocused = false" placeholder="用户名 / 邮箱" required />
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
v-model="username"
|
||||
@focus="usernameFocused = true"
|
||||
@blur="usernameFocused = false"
|
||||
placeholder="用户名 / 邮箱"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-container" :class="{ focused: passwordFocused || password }">
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M7 11V7C7 4.23858 9.23858 2 12 2C14.7614 2 17 4.23858 17 7V11" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
||||
<rect
|
||||
x="3"
|
||||
y="11"
|
||||
width="18"
|
||||
height="11"
|
||||
rx="2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.6"
|
||||
/>
|
||||
<path
|
||||
d="M7 11V7C7 4.23858 9.23858 2 12 2C14.7614 2 17 4.23858 17 7V11"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.6"
|
||||
/>
|
||||
</svg>
|
||||
<input id="password" type="password" v-model="password" @focus="passwordFocused = true" @blur="passwordFocused = false" placeholder="密码" required />
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
v-model="password"
|
||||
@focus="passwordFocused = true"
|
||||
@blur="passwordFocused = false"
|
||||
placeholder="密码"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row-between">
|
||||
@@ -126,9 +174,9 @@ const handleLogin = () => {
|
||||
display: flex;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(248,250,252,0.98));
|
||||
box-shadow: 0 18px 40px rgba(16,24,40,0.08);
|
||||
border: 1px solid rgba(15,23,42,0.04);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.98));
|
||||
box-shadow: 0 18px 40px rgba(16, 24, 40, 0.08);
|
||||
border: 1px solid rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
/* 左侧品牌区域 */
|
||||
@@ -136,129 +184,271 @@ const handleLogin = () => {
|
||||
width: 44%;
|
||||
min-width: 280px;
|
||||
padding: 28px 22px;
|
||||
background: linear-gradient(160deg, rgba(59,130,246,0.06), rgba(99,102,241,0.04));
|
||||
background: linear-gradient(160deg, rgba(59, 130, 246, 0.06), rgba(99, 102, 241, 0.04));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.brand { display:flex; align-items:center; gap:12px; margin-bottom:12px; }
|
||||
.brand-logo { width:56px; height:56px; display:flex; align-items:center; justify-content:center; color: #3b82f6; background: rgba(59,130,246,0.08); border-radius:12px; }
|
||||
.brand-name { font-size:18px; font-weight:700; color:#0f172a; }
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #3b82f6;
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
border-radius: 12px;
|
||||
}
|
||||
.brand-name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.side-desc { color:#475569; font-size:13px; line-height:1.5; max-width:210px; margin-bottom:18px; }
|
||||
.side-desc {
|
||||
color: #475569;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
max-width: 210px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
/* 简单的装饰圆块 */
|
||||
.side-illu { position:absolute; right:-30px; bottom:-20px; }
|
||||
.bubble { border-radius:50%; opacity:0.12; }
|
||||
.b1 { width:120px; height:120px; background: linear-gradient(135deg,#60a5fa,#7c3aed); transform: rotate(10deg); margin:8px; }
|
||||
.b2 { width:72px; height:72px; background: linear-gradient(135deg,#a78bfa,#60a5fa); margin:8px; }
|
||||
.b3 { width:40px; height:40px; background: linear-gradient(135deg,#93c5fd,#a78bfa); margin:8px; }
|
||||
.side-illu {
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
bottom: -20px;
|
||||
}
|
||||
.bubble {
|
||||
border-radius: 50%;
|
||||
opacity: 0.12;
|
||||
}
|
||||
.b1 {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: linear-gradient(135deg, #60a5fa, #7c3aed);
|
||||
transform: rotate(10deg);
|
||||
margin: 8px;
|
||||
}
|
||||
.b2 {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: linear-gradient(135deg, #a78bfa, #60a5fa);
|
||||
margin: 8px;
|
||||
}
|
||||
.b3 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #93c5fd, #a78bfa);
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
/* 右侧表单 */
|
||||
.login-body {
|
||||
width: 56%;
|
||||
padding: 30px 34px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
justify-content:space-between;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.login-header { text-align:left; margin-bottom:6px; }
|
||||
.login-title { font-size:20px; margin:0 0 6px; color:#0f172a; font-weight:700; }
|
||||
.login-subtitle { margin:0; font-size:13px; color:#64748b; font-weight:500; }
|
||||
.login-header {
|
||||
text-align: left;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.login-title {
|
||||
font-size: 20px;
|
||||
margin: 0 0 6px;
|
||||
color: #0f172a;
|
||||
font-weight: 700;
|
||||
}
|
||||
.login-subtitle {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表单 */
|
||||
.login-form { margin-top:10px; display:flex; flex-direction:column; gap:14px; }
|
||||
.login-form {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* 输入容器 */
|
||||
.input-container {
|
||||
position:relative;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
border-radius:10px;
|
||||
padding:12px 12px 12px 44px;
|
||||
border:1px solid rgba(15,23,42,0.06);
|
||||
box-shadow: 0 6px 18px rgba(15,23,42,0.02);
|
||||
transition: all .22s ease;
|
||||
border-radius: 10px;
|
||||
padding: 12px 12px 12px 44px;
|
||||
border: 1px solid rgba(15, 23, 42, 0.06);
|
||||
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.02);
|
||||
transition: all 0.22s ease;
|
||||
}
|
||||
.input-container .icon {
|
||||
position:absolute;
|
||||
left:12px;
|
||||
width:18px; height:18px;
|
||||
color:#94a3b8;
|
||||
opacity:0.95;
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #94a3b8;
|
||||
opacity: 0.95;
|
||||
}
|
||||
.input-container input {
|
||||
width:100%;
|
||||
border:none;
|
||||
outline:none;
|
||||
font-size:14px;
|
||||
color:#0f172a;
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
color: #0f172a;
|
||||
background: transparent;
|
||||
padding:0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 聚焦态 */
|
||||
.input-container.focused {
|
||||
border-color: rgba(99,102,241,0.9);
|
||||
box-shadow: 0 8px 20px rgba(99,102,241,0.08);
|
||||
border-color: rgba(99, 102, 241, 0.9);
|
||||
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.08);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.input-container.focused .icon { color: rgba(99,102,241,0.95); }
|
||||
.input-container.focused .icon {
|
||||
color: rgba(99, 102, 241, 0.95);
|
||||
}
|
||||
|
||||
/* 行内布局 */
|
||||
.row-between { display:flex; justify-content:space-between; align-items:center; font-size:13px; margin-top:2px; }
|
||||
.row-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* 记住我 checkbox */
|
||||
.remember { display:flex; align-items:center; gap:8px; color:#475569; cursor:pointer; user-select:none; }
|
||||
.remember input { position:absolute; opacity:0; pointer-events:none; }
|
||||
.remember .box { width:18px; height:18px; display:inline-block; border-radius:6px; border:1.5px solid rgba(15,23,42,0.08); background:white; box-shadow:inset 0 -1px 0 rgba(0,0,0,0.03); transition:all .14s ease; }
|
||||
.remember input:checked + .box { background: linear-gradient(90deg,#6366f1,#3b82f6); border-color:transparent; box-shadow:none; }
|
||||
.remember input:checked + .box::after { content:""; display:block; width:6px; height:10px; border:2px solid white; border-left:0; border-top:0; transform: translate(5px,2px) rotate(45deg); }
|
||||
.remember {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.remember input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.remember .box {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-block;
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid rgba(15, 23, 42, 0.08);
|
||||
background: white;
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.14s ease;
|
||||
}
|
||||
.remember input:checked + .box {
|
||||
background: linear-gradient(90deg, #6366f1, #3b82f6);
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.remember input:checked + .box::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 6px;
|
||||
height: 10px;
|
||||
border: 2px solid white;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
transform: translate(5px, 2px) rotate(45deg);
|
||||
}
|
||||
|
||||
/* 链接样式 */
|
||||
.link { color:#3b82f6; text-decoration:none; font-weight:600; }
|
||||
.link:hover { text-decoration:underline; color:#2563eb; }
|
||||
.link {
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.link:hover {
|
||||
text-decoration: underline;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
/* 主操作按钮 */
|
||||
.btn-primary {
|
||||
width:100%;
|
||||
padding:12px 14px;
|
||||
border-radius:10px;
|
||||
border:none;
|
||||
background: linear-gradient(90deg,#6366f1,#3b82f6);
|
||||
color:white;
|
||||
font-weight:700;
|
||||
font-size:15px;
|
||||
cursor:pointer;
|
||||
box-shadow: 0 8px 20px rgba(59,130,246,0.18);
|
||||
transition: transform .12s ease, box-shadow .12s ease, opacity .12s;
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
background: linear-gradient(90deg, #6366f1, #3b82f6);
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.18);
|
||||
transition:
|
||||
transform 0.12s ease,
|
||||
box-shadow 0.12s ease,
|
||||
opacity 0.12s;
|
||||
}
|
||||
.btn-primary:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-primary:active { transform: translateY(0); }
|
||||
.btn-primary:disabled { opacity:0.6; cursor:default; box-shadow:none; }
|
||||
|
||||
/* 错误提示 */
|
||||
.error {
|
||||
color:#b91c1c;
|
||||
color: #b91c1c;
|
||||
background: #fff5f5;
|
||||
border-left:4px solid #f87171;
|
||||
padding:10px 12px;
|
||||
border-radius:8px;
|
||||
font-weight:600;
|
||||
font-size:13px;
|
||||
margin-top:6px;
|
||||
border-left: 4px solid #f87171;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* 页脚注册 */
|
||||
.login-footer { display:flex; gap:8px; align-items:center; justify-content:flex-end; font-size:13px; color:#475569; }
|
||||
.login-footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 820px) {
|
||||
.login-card { flex-direction:column; height:auto; width:100%; }
|
||||
.login-side { width:100%; min-height:140px; order:1; }
|
||||
.login-body { width:100%; order:2; padding:22px; }
|
||||
.login-card {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.login-side {
|
||||
width: 100%;
|
||||
min-height: 140px;
|
||||
order: 1;
|
||||
}
|
||||
.login-body {
|
||||
width: 100%;
|
||||
order: 2;
|
||||
padding: 22px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user