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:
parent
eaf99b5efa
commit
aaf2216b86
|
|
@ -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>
|
||||
|
|
@ -1,33 +1,64 @@
|
|||
<template>
|
||||
<div class="layout">
|
||||
<header class="topbar">
|
||||
<div class="logo-title">房地产新材料选材管理数据系统</div>
|
||||
<div class="brand">
|
||||
<div class="mark">M³</div>
|
||||
<div class="logo-title">房地产新材料选材管理数据系统</div>
|
||||
</div>
|
||||
<div class="user">
|
||||
<div class="avatar">{{ userInitial }}</div>
|
||||
<div class="user-info">
|
||||
<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>
|
||||
<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>
|
||||
</header>
|
||||
<div class="body">
|
||||
<aside class="sidebar">
|
||||
<el-menu
|
||||
:default-active="active"
|
||||
class="menu"
|
||||
router
|
||||
>
|
||||
<el-menu-item v-if="isAdmin" index="/users">用户管理</el-menu-item>
|
||||
<el-menu-item index="/factories">供应商库</el-menu-item>
|
||||
<el-menu-item v-if="isAdmin" index="/brands">品牌库</el-menu-item>
|
||||
<el-menu-item index="/materials">材料管理</el-menu-item>
|
||||
<el-menu-item v-if="isAdmin" index="/screen/overview">数据大屏</el-menu-item>
|
||||
<el-menu :default-active="active" class="menu" router>
|
||||
<el-menu-item v-if="isAdmin" index="/users">
|
||||
<BaseIcon name="user" :size="18" />
|
||||
<span>用户管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/factories">
|
||||
<BaseIcon name="factory" :size="18" />
|
||||
<span>供应商库</span>
|
||||
</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">
|
||||
<template #title>配置项</template>
|
||||
<el-menu-item index="/dictionary">材料分类</el-menu-item>
|
||||
<template #title>
|
||||
<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-menu>
|
||||
<div class="sidebar-footer">v1.0 · 2026</div>
|
||||
</aside>
|
||||
<main class="main">
|
||||
<section class="content">
|
||||
|
|
@ -62,6 +93,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { useAuth } from '@/store/auth'
|
||||
import { changePassword } from '@/api/auth'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -69,6 +101,10 @@ const { state, isAdmin, clearAuth } = useAuth()
|
|||
|
||||
const active = computed(() => route.path)
|
||||
const user = computed(() => state.user)
|
||||
const userInitial = computed(() => {
|
||||
const name = state.user?.username || ''
|
||||
return name ? name.charAt(0).toUpperCase() : '?'
|
||||
})
|
||||
|
||||
const passwordVisible = ref(false)
|
||||
const passwordForm = reactive({
|
||||
|
|
@ -105,25 +141,49 @@ const onLogout = () => {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.topbar {
|
||||
height: 64px;
|
||||
height: 60px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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;
|
||||
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 {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--brand-900, #1b2a41);
|
||||
letter-spacing: 0.4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user {
|
||||
|
|
@ -132,18 +192,58 @@ const onLogout = () => {
|
|||
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 {
|
||||
text-align: right;
|
||||
line-height: 1.2;
|
||||
line-height: 1.25;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-info .name {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: var(--text-900);
|
||||
}
|
||||
|
||||
.user-info .role {
|
||||
font-size: 12px;
|
||||
color: #6b7785;
|
||||
font-size: 11px;
|
||||
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 {
|
||||
|
|
@ -159,47 +259,73 @@ const onLogout = () => {
|
|||
color: #fff;
|
||||
display: flex;
|
||||
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 {
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
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;
|
||||
transition: all 0.2s ease;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
:deep(.el-menu-item.is-active) {
|
||||
color: #7cb4e3;
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, rgba(124, 180, 227, 0.25), rgba(78, 134, 184, 0.18));
|
||||
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) {
|
||||
color: #7cb4e3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
:deep(.el-sub-menu .el-menu) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -4,17 +4,25 @@
|
|||
--brand-800: #203754;
|
||||
--brand-700: #2f4b6b;
|
||||
--brand-500: #4e86b8;
|
||||
--brand-400: #6ea7d8;
|
||||
--brand-300: #7cb4e3;
|
||||
--accent-500: #f2b24c;
|
||||
--accent-400: #ffd18a;
|
||||
--bg: #f3f5fb;
|
||||
--bg-soft: #eef2f9;
|
||||
--border-soft: rgba(15, 26, 42, 0.06);
|
||||
--text-900: #0f1a2a;
|
||||
--text-600: #5c6b7a;
|
||||
--text-400: #8b98a7;
|
||||
--card: #ffffff;
|
||||
--danger: #d64550;
|
||||
--warning: #e8930f;
|
||||
--success: #24a26a;
|
||||
--radius-lg: 14px;
|
||||
--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%;
|
||||
padding: 20px 24px 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.list-page .toolbar {
|
||||
flex-shrink: 0;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.list-page .page-title {
|
||||
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 {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.list-page .pagination {
|
||||
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 {
|
||||
|
|
@ -123,6 +150,12 @@ a {
|
|||
.el-table {
|
||||
border-radius: var(--radius-md);
|
||||
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 {
|
||||
|
|
@ -156,13 +189,28 @@ a {
|
|||
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 {
|
||||
background: linear-gradient(135deg, var(--brand-500), #6ea7d8);
|
||||
background: linear-gradient(135deg, var(--brand-500), var(--brand-400));
|
||||
border: none;
|
||||
box-shadow: 0 6px 14px rgba(78, 134, 184, 0.25);
|
||||
}
|
||||
|
||||
.el-button--primary:hover {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -194,16 +194,6 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<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) {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
|
|
|
|||
|
|
@ -213,12 +213,6 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-scroll :deep(.el-dialog__body) {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,79 @@
|
|||
<template>
|
||||
<template>
|
||||
<div class="login">
|
||||
<div class="login-card">
|
||||
<div class="title">房地产新材料选材管理数据系统</div>
|
||||
<div class="subtitle">登录系统</div>
|
||||
<el-form :model="form" @submit.prevent>
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
||||
</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>
|
||||
<aside class="brand-panel">
|
||||
<div class="brand-top">
|
||||
<div class="brand-mark">M³</div>
|
||||
<div class="brand-name">
|
||||
<div class="brand-title">房地产新材料选材管理</div>
|
||||
<div class="brand-sub">Material Management Platform</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>
|
||||
|
||||
|
|
@ -23,14 +83,13 @@ import { useRouter } from 'vue-router'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { login } from '@/api/auth'
|
||||
import { useAuth } from '@/store/auth'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { setAuth } = useAuth()
|
||||
const form = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
const form = reactive({ username: '', password: '' })
|
||||
const loading = ref(false)
|
||||
const year = new Date().getFullYear()
|
||||
|
||||
const onLogin = async () => {
|
||||
if (!form.username || !form.password) {
|
||||
|
|
@ -54,39 +113,219 @@ const onLogin = async () => {
|
|||
.login {
|
||||
min-height: 100vh;
|
||||
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;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1b2a41 0%, #3c6f8f 60%, #7cb4e3 100%);
|
||||
background: #f3f5fb;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 405px;
|
||||
padding: 32px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 18px 40px rgba(15, 26, 42, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 44px 40px;
|
||||
border-radius: 20px;
|
||||
background: #fff;
|
||||
box-shadow: 0 24px 60px rgba(15, 26, 42, 0.08);
|
||||
border: 1px solid rgba(15, 26, 42, 0.04);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
.card-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #1b2a41;
|
||||
color: #0f1a2a;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
.card-sub {
|
||||
font-size: 14px;
|
||||
color: #5c6b7a;
|
||||
margin-bottom: 20px;
|
||||
margin: 8px 0 28px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
.submit {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 4px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 12px;
|
||||
.copyright {
|
||||
margin-top: 24px;
|
||||
font-size: 12px;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -741,12 +741,6 @@ onMounted(() => {
|
|||
gap: 12px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-scroll :deep(.el-dialog__body) {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
|
|
|
|||
|
|
@ -213,12 +213,6 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dialog-scroll :deep(.el-dialog__body) {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
|
|
|
|||
Loading…
Reference in New Issue