style: 整体视觉升级(登录页分屏、菜单图标、列表卡片化)

- 新增 BaseIcon 组件(inline SVG,不引入新依赖)
- 登录页:左右分屏(深色品牌视觉区 + 白色登录卡)、输入框前缀图标、
  slogan 渐变文字、装饰光晕
- MainLayout 顶栏:品牌 mark + 用户头像 + 角色状态点,按钮带图标
- MainLayout 侧栏:菜单项加图标、active 态渐变+左色条、圆角+外边距
- 全局 token 扩展:brand/text 色阶、border-soft、shadow-card
- 列表页工具栏/表格/分页改为卡片式(白底+阴影+圆角)
- 表格 hover 行态、按钮 active 轻按下、主按钮带蓝色投影
- 清理 scoped 里冗余的 toolbar/pagination 样式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-04-24 08:06:46 +08:00
parent eaf99b5efa
commit aaf2216b86
8 changed files with 582 additions and 111 deletions

View File

@ -0,0 +1,86 @@
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
stroke-linejoin="round"
class="base-icon"
:aria-label="name"
>
<path v-for="(d, i) in currentPaths" :key="i" :d="d" />
</svg>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: { type: String, required: true },
size: { type: [Number, String], default: 18 }
})
const ICON_MAP = {
user: [
'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2',
'M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z'
],
factory: [
'M3 21V11l6-3v3l6-3v13H3Z',
'M15 21h6V12l-6-4'
],
tag: [
'M20.59 13.41 13.41 20.59a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82Z',
'M7 7h.01'
],
box: [
'M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z',
'M3.3 7 12 12l8.7-5',
'M12 22V12'
],
chart: [
'M3 3v18h18',
'M7 14v-4',
'M11 14v-8',
'M15 14v-2',
'M19 14V6'
],
setting: [
'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',
'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z'
],
grid: [
'M3 3h7v7H3z',
'M14 3h7v7h-7z',
'M14 14h7v7h-7z',
'M3 14h7v7H3z'
],
lock: [
'M5 11h14v10H5z',
'M8 11V7a4 4 0 1 1 8 0v4'
],
logout: [
'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4',
'M16 17l5-5-5-5',
'M21 12H9'
],
key: [
'M21 2l-9.5 9.5',
'M15.5 7.5l1.5 1.5',
'M7 15a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z'
]
}
const currentPaths = computed(() => ICON_MAP[props.name] || [])
</script>
<style scoped>
.base-icon {
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
}
</style>

View File

@ -1,33 +1,64 @@
<template> <template>
<div class="layout"> <div class="layout">
<header class="topbar"> <header class="topbar">
<div class="brand">
<div class="mark"></div>
<div class="logo-title">房地产新材料选材管理数据系统</div> <div class="logo-title">房地产新材料选材管理数据系统</div>
</div>
<div class="user"> <div class="user">
<div class="avatar">{{ userInitial }}</div>
<div class="user-info"> <div class="user-info">
<div class="name">{{ user?.username || '用户' }}</div> <div class="name">{{ user?.username || '用户' }}</div>
<div class="role">{{ isAdmin ? '管理员' : '普通账号' }}</div> <div class="role">
<span class="role-dot" :class="{ admin: isAdmin }"></span>
{{ isAdmin ? '管理员' : '普通账号' }}
</div> </div>
<el-button size="small" @click="openPassword">修改密码</el-button> </div>
<el-button size="small" @click="onLogout">退出</el-button> <el-button size="small" class="topbar-btn" @click="openPassword">
<BaseIcon name="key" :size="14" />
<span>修改密码</span>
</el-button>
<el-button size="small" class="topbar-btn" @click="onLogout">
<BaseIcon name="logout" :size="14" />
<span>退出</span>
</el-button>
</div> </div>
</header> </header>
<div class="body"> <div class="body">
<aside class="sidebar"> <aside class="sidebar">
<el-menu <el-menu :default-active="active" class="menu" router>
:default-active="active" <el-menu-item v-if="isAdmin" index="/users">
class="menu" <BaseIcon name="user" :size="18" />
router <span>用户管理</span>
> </el-menu-item>
<el-menu-item v-if="isAdmin" index="/users">用户管理</el-menu-item> <el-menu-item index="/factories">
<el-menu-item index="/factories">供应商库</el-menu-item> <BaseIcon name="factory" :size="18" />
<el-menu-item v-if="isAdmin" index="/brands">品牌库</el-menu-item> <span>供应商库</span>
<el-menu-item index="/materials">材料管理</el-menu-item> </el-menu-item>
<el-menu-item v-if="isAdmin" index="/screen/overview">数据大屏</el-menu-item> <el-menu-item v-if="isAdmin" index="/brands">
<BaseIcon name="tag" :size="18" />
<span>品牌库</span>
</el-menu-item>
<el-menu-item index="/materials">
<BaseIcon name="box" :size="18" />
<span>材料管理</span>
</el-menu-item>
<el-menu-item v-if="isAdmin" index="/screen/overview">
<BaseIcon name="chart" :size="18" />
<span>数据大屏</span>
</el-menu-item>
<el-sub-menu v-if="isAdmin" index="config"> <el-sub-menu v-if="isAdmin" index="config">
<template #title>配置项</template> <template #title>
<el-menu-item index="/dictionary">材料分类</el-menu-item> <BaseIcon name="setting" :size="18" />
<span>配置项</span>
</template>
<el-menu-item index="/dictionary">
<BaseIcon name="grid" :size="16" />
<span>材料分类</span>
</el-menu-item>
</el-sub-menu> </el-sub-menu>
</el-menu> </el-menu>
<div class="sidebar-footer">v1.0 · 2026</div>
</aside> </aside>
<main class="main"> <main class="main">
<section class="content"> <section class="content">
@ -62,6 +93,7 @@ import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useAuth } from '@/store/auth' import { useAuth } from '@/store/auth'
import { changePassword } from '@/api/auth' import { changePassword } from '@/api/auth'
import BaseIcon from '@/components/BaseIcon.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -69,6 +101,10 @@ const { state, isAdmin, clearAuth } = useAuth()
const active = computed(() => route.path) const active = computed(() => route.path)
const user = computed(() => state.user) const user = computed(() => state.user)
const userInitial = computed(() => {
const name = state.user?.username || ''
return name ? name.charAt(0).toUpperCase() : '?'
})
const passwordVisible = ref(false) const passwordVisible = ref(false)
const passwordForm = reactive({ const passwordForm = reactive({
@ -105,25 +141,49 @@ const onLogout = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
background: var(--bg);
} }
.topbar { .topbar {
height: 64px; height: 60px;
padding: 0 24px; padding: 0 24px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
background: #fff; background: #fff;
box-shadow: 0 12px 24px rgba(15, 26, 42, 0.08); border-bottom: 1px solid rgba(15, 26, 42, 0.06);
flex-shrink: 0; flex-shrink: 0;
z-index: 10; z-index: 10;
} }
.brand {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.mark {
width: 34px;
height: 34px;
border-radius: 9px;
background: linear-gradient(135deg, var(--brand-500), #6ea7d8);
color: #fff;
display: grid;
place-items: center;
font-size: 16px;
font-weight: 800;
box-shadow: 0 8px 18px rgba(78, 134, 184, 0.3);
}
.logo-title { .logo-title {
font-size: 18px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: var(--brand-900, #1b2a41); color: var(--brand-900, #1b2a41);
letter-spacing: 0.4px;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.user { .user {
@ -132,18 +192,58 @@ const onLogout = () => {
gap: 12px; gap: 12px;
} }
.avatar {
width: 34px;
height: 34px;
border-radius: 50%;
background: linear-gradient(135deg, #4e86b8, #7cb4e3);
color: #fff;
font-weight: 700;
font-size: 14px;
display: grid;
place-items: center;
box-shadow: 0 6px 14px rgba(78, 134, 184, 0.3);
}
.user-info { .user-info {
text-align: right; text-align: right;
line-height: 1.2; line-height: 1.25;
min-width: 0;
} }
.user-info .name { .user-info .name {
font-weight: 600; font-weight: 600;
font-size: 13px;
color: var(--text-900);
} }
.user-info .role { .user-info .role {
font-size: 12px; font-size: 11px;
color: #6b7785; color: var(--text-600);
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
margin-top: 2px;
}
.role-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #9aa6b2;
}
.role-dot.admin {
background: var(--accent-500);
box-shadow: 0 0 0 3px rgba(242, 178, 76, 0.18);
}
.topbar-btn {
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: 8px;
} }
.body { .body {
@ -159,47 +259,73 @@ const onLogout = () => {
color: #fff; color: #fff;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
}
.sidebar::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at top left, rgba(124, 180, 227, 0.12), transparent 60%);
pointer-events: none;
} }
.menu { .menu {
border-right: none; border-right: none;
background: transparent; background: transparent;
color: #fff; color: #fff;
padding-top: 12px; padding: 16px 10px 0;
flex: 1;
position: relative;
z-index: 1;
} }
:deep(.el-menu-item) { :deep(.el-menu-item),
:deep(.el-sub-menu__title) {
color: rgba(255, 255, 255, 0.82);
font-size: 14px;
height: 46px;
line-height: 46px;
border-radius: 10px;
padding: 0 14px !important;
margin-bottom: 4px;
transition: background 0.2s, color 0.2s;
gap: 10px;
}
:deep(.el-menu-item:hover),
:deep(.el-sub-menu__title:hover) {
color: #fff; color: #fff;
transition: all 0.2s ease; background: rgba(255, 255, 255, 0.08);
} }
:deep(.el-menu-item.is-active) { :deep(.el-menu-item.is-active) {
color: #7cb4e3; color: #fff;
background-color: rgba(255, 255, 255, 0.12); background: linear-gradient(135deg, rgba(124, 180, 227, 0.25), rgba(78, 134, 184, 0.18));
box-shadow: inset 3px 0 0 #7cb4e3; box-shadow: inset 3px 0 0 #7cb4e3;
} }
:deep(.el-menu-item:hover) {
color: #fff;
background-color: rgba(255, 255, 255, 0.08);
}
:deep(.el-sub-menu__title) {
color: #fff;
transition: all 0.2s ease;
}
:deep(.el-sub-menu__title:hover) {
color: #fff;
background-color: rgba(255, 255, 255, 0.08);
}
:deep(.el-sub-menu.is-active > .el-sub-menu__title) { :deep(.el-sub-menu.is-active > .el-sub-menu__title) {
color: #7cb4e3; color: #fff;
} }
:deep(.el-sub-menu .el-menu) { :deep(.el-sub-menu .el-menu) {
background: transparent; background: transparent;
padding-left: 0;
}
:deep(.el-sub-menu .el-menu-item) {
padding-left: 40px !important;
font-size: 13px;
}
.sidebar-footer {
padding: 14px 18px;
font-size: 11px;
color: rgba(255, 255, 255, 0.35);
letter-spacing: 0.5px;
position: relative;
z-index: 1;
} }
.main { .main {

View File

@ -4,17 +4,25 @@
--brand-800: #203754; --brand-800: #203754;
--brand-700: #2f4b6b; --brand-700: #2f4b6b;
--brand-500: #4e86b8; --brand-500: #4e86b8;
--brand-400: #6ea7d8;
--brand-300: #7cb4e3;
--accent-500: #f2b24c; --accent-500: #f2b24c;
--accent-400: #ffd18a; --accent-400: #ffd18a;
--bg: #f3f5fb; --bg: #f3f5fb;
--bg-soft: #eef2f9; --bg-soft: #eef2f9;
--border-soft: rgba(15, 26, 42, 0.06);
--text-900: #0f1a2a; --text-900: #0f1a2a;
--text-600: #5c6b7a; --text-600: #5c6b7a;
--text-400: #8b98a7;
--card: #ffffff; --card: #ffffff;
--danger: #d64550; --danger: #d64550;
--warning: #e8930f;
--success: #24a26a; --success: #24a26a;
--radius-lg: 14px; --radius-lg: 14px;
--radius-md: 10px; --radius-md: 10px;
--radius-sm: 8px;
--shadow-card: 0 6px 24px rgba(15, 26, 42, 0.06);
--shadow-card-lg: 0 18px 40px rgba(15, 26, 42, 0.08);
} }
* { * {
@ -54,25 +62,44 @@ a {
height: 100%; height: 100%;
padding: 20px 24px 16px; padding: 20px 24px 16px;
min-height: 0; min-height: 0;
} gap: 14px;
.list-page .toolbar {
flex-shrink: 0;
} }
.list-page .page-title { .list-page .page-title {
flex-shrink: 0; flex-shrink: 0;
margin: 0;
}
.list-page .toolbar {
flex-shrink: 0;
margin: 0;
padding: 14px 16px;
background: var(--card);
border: 1px solid var(--border-soft);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
} }
.list-page .table-wrap { .list-page .table-wrap {
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
background: var(--card);
border: 1px solid var(--border-soft);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
} }
.list-page .pagination { .list-page .pagination {
flex-shrink: 0; flex-shrink: 0;
margin-top: 12px; margin: 0;
padding: 10px 16px;
background: var(--card);
border: 1px solid var(--border-soft);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card);
display: flex;
justify-content: flex-end;
} }
.page-title { .page-title {
@ -123,6 +150,12 @@ a {
.el-table { .el-table {
border-radius: var(--radius-md); border-radius: var(--radius-md);
overflow: hidden; overflow: hidden;
--el-table-border-color: var(--border-soft);
--el-table-header-bg-color: var(--bg-soft);
}
.el-table tr:hover > td {
background-color: rgba(78, 134, 184, 0.04) !important;
} }
.table-maxheight { .table-maxheight {
@ -156,13 +189,28 @@ a {
border-radius: var(--radius-md); border-radius: var(--radius-md);
} }
.el-button {
border-radius: var(--radius-sm);
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.el-button:active {
transform: translateY(1px);
}
.el-button--primary { .el-button--primary {
background: linear-gradient(135deg, var(--brand-500), #6ea7d8); background: linear-gradient(135deg, var(--brand-500), var(--brand-400));
border: none; border: none;
box-shadow: 0 6px 14px rgba(78, 134, 184, 0.25);
} }
.el-button--primary:hover { .el-button--primary:hover {
background: linear-gradient(135deg, #3f7bb1, #7db7e7); background: linear-gradient(135deg, #3f7bb1, #7db7e7);
box-shadow: 0 8px 18px rgba(78, 134, 184, 0.32);
}
.el-pagination {
--el-pagination-button-bg-color: transparent;
} }
.el-dialog { .el-dialog {

View File

@ -194,16 +194,6 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
.toolbar {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.dialog-scroll :deep(.el-dialog__body) { .dialog-scroll :deep(.el-dialog__body) {
max-height: 60vh; max-height: 60vh;
overflow: auto; overflow: auto;

View File

@ -213,12 +213,6 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.dialog-scroll :deep(.el-dialog__body) { .dialog-scroll :deep(.el-dialog__body) {
max-height: 60vh; max-height: 60vh;
overflow: auto; overflow: auto;

View File

@ -1,20 +1,80 @@
<template> <template>
<div class="login"> <div class="login">
<div class="login-card"> <aside class="brand-panel">
<div class="title">房地产新材料选材管理数据系统</div> <div class="brand-top">
<div class="subtitle">登录系统</div> <div class="brand-mark"></div>
<el-form :model="form" @submit.prevent> <div class="brand-name">
<el-form-item label="用户名"> <div class="brand-title">房地产新材料选材管理</div>
<el-input v-model="form.username" placeholder="请输入用户名" /> <div class="brand-sub">Material Management Platform</div>
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
</el-form-item>
<el-button type="primary" class="btn" :loading="loading" @click="onLogin">登录</el-button>
</el-form>
<div class="hint">默认接口地址/api</div>
</div> </div>
</div> </div>
<div class="brand-slogan">
<div class="slogan-main">数字化管理材料供应链</div>
<div class="slogan-desc">统一的供应商品牌与材料主数据让选材决策更高效更透明</div>
</div>
<ul class="brand-highlights">
<li>
<span class="dot" />
<div>
<b>供应商库 / 品牌库</b>
<p>规范维护材料来源主数据</p>
</div>
</li>
<li>
<span class="dot" />
<div>
<b>材料目录 / 审核流程</b>
<p>分级填报规范审批</p>
</div>
</li>
<li>
<span class="dot" />
<div>
<b>数据大屏</b>
<p>多维度统计一屏掌握</p>
</div>
</li>
</ul>
<div class="orb orb-1" />
<div class="orb orb-2" />
<div class="grid-bg" />
</aside>
<section class="form-panel">
<div class="login-card">
<div class="card-head">
<div class="card-title">欢迎登录</div>
<div class="card-sub">请输入账号信息进入系统</div>
</div>
<el-form :model="form" size="large" @submit.prevent>
<el-form-item>
<el-input v-model="form.username" placeholder="用户名" @keyup.enter="onLogin">
<template #prefix>
<BaseIcon name="user" :size="16" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-input
v-model="form.password"
type="password"
placeholder="密码"
show-password
@keyup.enter="onLogin"
>
<template #prefix>
<BaseIcon name="lock" :size="16" />
</template>
</el-input>
</el-form-item>
<el-button type="primary" class="submit" :loading="loading" @click="onLogin">
</el-button>
</el-form>
<div class="copyright">© {{ year }} 房地产新材料选材管理数据系统</div>
</div>
</section>
</div>
</template> </template>
<script setup> <script setup>
@ -23,14 +83,13 @@ import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { login } from '@/api/auth' import { login } from '@/api/auth'
import { useAuth } from '@/store/auth' import { useAuth } from '@/store/auth'
import BaseIcon from '@/components/BaseIcon.vue'
const router = useRouter() const router = useRouter()
const { setAuth } = useAuth() const { setAuth } = useAuth()
const form = reactive({ const form = reactive({ username: '', password: '' })
username: '',
password: ''
})
const loading = ref(false) const loading = ref(false)
const year = new Date().getFullYear()
const onLogin = async () => { const onLogin = async () => {
if (!form.username || !form.password) { if (!form.username || !form.password) {
@ -54,39 +113,219 @@ const onLogin = async () => {
.login { .login {
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
background: #0e1a2a;
color: #fff;
}
.brand-panel {
position: relative;
flex: 1.1 1 0;
padding: 56px 60px 48px;
display: flex;
flex-direction: column;
gap: 32px;
background: linear-gradient(135deg, #0e1a2a 0%, #1b2a41 45%, #2f4b6b 100%);
overflow: hidden;
}
.brand-top {
display: flex;
align-items: center;
gap: 14px;
z-index: 1;
}
.brand-mark {
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #7cb4e3, #4e86b8);
display: grid;
place-items: center;
font-size: 22px;
font-weight: 800;
letter-spacing: 0;
box-shadow: 0 12px 24px rgba(78, 134, 184, 0.35);
}
.brand-title {
font-size: 18px;
font-weight: 700;
letter-spacing: 0.5px;
}
.brand-sub {
font-size: 12px;
color: rgba(255, 255, 255, 0.55);
letter-spacing: 1px;
margin-top: 4px;
}
.brand-slogan {
margin-top: 28px;
z-index: 1;
max-width: 440px;
}
.slogan-main {
font-size: 32px;
font-weight: 700;
line-height: 1.3;
background: linear-gradient(135deg, #ffffff 0%, #7cb4e3 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.slogan-desc {
margin-top: 14px;
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
line-height: 1.8;
}
.brand-highlights {
list-style: none;
padding: 0;
margin: auto 0 0;
display: flex;
flex-direction: column;
gap: 18px;
z-index: 1;
}
.brand-highlights li {
display: flex;
gap: 14px;
align-items: flex-start;
}
.brand-highlights b {
display: block;
font-size: 14px;
font-weight: 600;
}
.brand-highlights p {
margin: 2px 0 0;
color: rgba(255, 255, 255, 0.55);
font-size: 12px;
line-height: 1.6;
}
.dot {
width: 8px;
height: 8px;
background: #7cb4e3;
border-radius: 50%;
margin-top: 6px;
flex-shrink: 0;
box-shadow: 0 0 0 4px rgba(124, 180, 227, 0.18);
}
.orb {
position: absolute;
border-radius: 50%;
filter: blur(60px);
pointer-events: none;
}
.orb-1 {
width: 340px;
height: 340px;
background: radial-gradient(circle, rgba(124, 180, 227, 0.55), transparent 70%);
top: -80px;
right: -60px;
}
.orb-2 {
width: 280px;
height: 280px;
background: radial-gradient(circle, rgba(242, 178, 76, 0.35), transparent 70%);
bottom: -80px;
left: -60px;
}
.grid-bg {
position: absolute;
inset: 0;
background-image: linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
background-size: 40px 40px;
mask-image: radial-gradient(circle at center, #000 55%, transparent 100%);
-webkit-mask-image: radial-gradient(circle at center, #000 55%, transparent 100%);
pointer-events: none;
}
.form-panel {
flex: 1 1 0;
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: linear-gradient(135deg, #1b2a41 0%, #3c6f8f 60%, #7cb4e3 100%); background: #f3f5fb;
padding: 40px 24px;
} }
.login-card { .login-card {
width: 405px; width: 100%;
padding: 32px; max-width: 420px;
border-radius: 16px; padding: 44px 40px;
background: rgba(255, 255, 255, 0.95); border-radius: 20px;
box-shadow: 0 18px 40px rgba(15, 26, 42, 0.2); background: #fff;
border: 1px solid rgba(255, 255, 255, 0.6); box-shadow: 0 24px 60px rgba(15, 26, 42, 0.08);
border: 1px solid rgba(15, 26, 42, 0.04);
} }
.title { .card-title {
font-size: 24px; font-size: 26px;
font-weight: 700; font-weight: 700;
color: #1b2a41; color: #0f1a2a;
} }
.subtitle { .card-sub {
font-size: 14px; font-size: 14px;
color: #5c6b7a; color: #5c6b7a;
margin-bottom: 20px; margin: 8px 0 28px;
} }
.btn { .submit {
width: 100%; width: 100%;
height: 44px;
font-size: 15px;
font-weight: 600;
letter-spacing: 4px;
border-radius: 10px;
} }
.hint { .copyright {
margin-top: 12px; margin-top: 24px;
font-size: 12px; font-size: 12px;
color: #9aa6b2; color: #9aa6b2;
text-align: center;
letter-spacing: 0.5px;
}
:deep(.el-input__wrapper) {
border-radius: 10px;
background: #f8f9fc;
box-shadow: 0 0 0 1px rgba(15, 26, 42, 0.08) inset;
padding-left: 14px;
}
:deep(.el-input__wrapper.is-focus),
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px rgba(78, 134, 184, 0.55) inset;
background: #fff;
}
:deep(.el-input__prefix) {
color: #8b98a7;
margin-right: 6px;
}
@media (max-width: 960px) {
.brand-panel {
display: none;
}
} }
</style> </style>

View File

@ -741,12 +741,6 @@ onMounted(() => {
gap: 12px; gap: 12px;
} }
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.dialog-scroll :deep(.el-dialog__body) { .dialog-scroll :deep(.el-dialog__body) {
max-height: 60vh; max-height: 60vh;
overflow: auto; overflow: auto;

View File

@ -213,12 +213,6 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.dialog-scroll :deep(.el-dialog__body) { .dialog-scroll :deep(.el-dialog__body) {
max-height: 60vh; max-height: 60vh;
overflow: auto; overflow: auto;